【題解】Codeforces 441D. Valera and Swaps 排列與環的轉換

對於一個1n1到n的排列pp,定義f(p)f(p)爲最少所需的交換次數(不要求相鄰),使得pi=ip_i=i

給定長爲n(3000)n(3000)的排列ppm(3000)m(3000),問最少需要幾次交換,可以使得f(p)=mf(p)=m.

輸出字典序最小的方案。


按如下方法建立nn個節點的有向圖:如果aa位置上的數字是bb,就添加一條aa指向bb的有向邊。

這張圖有如下性質:

  1. 每個節點的入度爲11,出度爲11,整張圖由若干個環組成(可能有自環)。
  2. 交換兩個位置上的數等價於交換兩個節點的出邊指向,且:
    1. 如果交換前的兩個節點在同一個環中,那麼交換後環的個數一定多11
    2. 如果交換前兩個節點不在同一個環中,那麼交換後環的個數一定少11
  3. pi=ip_i=i的排列構成的圖由nn個自環組成,環的個數是nn.
  4. 設圖中有loopsloops個環,那麼f(p)=nloopsf(p)=n-loops,即需要nloopsn-loops次操作將所有環都分解成自環。

由以上結論,可以知道最小交換次數就是f(p)m|f(p)-m|

  1. 如果f(p)<mf(p)<m,需要合併mf(p)m-f(p)個環,找到前mf(p)m-f(p)個環的最小元素,依次合併即可。
  2. 如果f(p)>mf(p)>m ,需要拆分f(p)mf(p)-m個環,從11開始遍歷,如果不是自環,就將它與它所屬的環中第二小的元素交換。

困死了,明天再寫,這和字符串有什麼關係啊。


使用集合維護每個環的點編號,果斷超時。

仔細想想,合併環時,只需要知道前兩個環的首個點編號。

拆分環時,只需要知道第一個至少有兩個點的環的前兩個點編號。

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 3016, MOD = 1000000007;

int save[M];
int l1p, l2p, lp1, lp2; //前兩個環的首個點,第一個至少有兩個點的環的前兩個點
int deal(int n)
{
	int vis[M]={}, lps = 0;
	l1p = l2p = lp1 = lp2 = 0;
	for(int i=1; i<=n; ++i) if(!vis[i])
	{
		++lps;
		if(lps==1) l1p = i;
		else if(lps==2) l2p = i;

		vector<int> lp;
		for(int now=i; !vis[now]; now=save[now])
		{
			vis[now] = 1;
			lp.push_back(now);
		}
		if(lp1==0 && lp.size()>=2)
		{
			sort(lp.begin(), lp.end());
			lp1 = lp[0];
			lp2 = lp[1];
		}
	}
	return lps;
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

    int n = read();
    for(int i=1; i<=n; ++i) 
		save[i] = read();

    int need_lp = n-read(), lps = deal(n);

    vector<pair<int,int>> ans;
    while(lps != need_lp) 
    {
    	int a, b;
    	if(lps < need_lp) a = lp1, b = lp2;
    	else a = l1p, b = l2p;

		ans.push_back({a, b});
		swap(save[a],save[b]);

		lps = deal(n);
    }
    printf("%d\n",ans.size() );
    for(auto p:ans)
    	printf("%d %d\n", p.first, p.second );

    return 0;
}

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章