【题解】Codeforces 441D. Valera and Swaps 排列与环的转换

对于一个 1 n 1到n 的排列 p p ,定义 f ( p ) f(p) 为最少所需的交换次数(不要求相邻),使得 p i = i p_i=i

给定长为 n ( 3000 ) n(3000) 的排列 p p m ( 3000 ) m(3000) ,问最少需要几次交换,可以使得 f ( p ) = m f(p)=m .

输出字典序最小的方案。


按如下方法建立 n n 个节点的有向图:如果 a a 位置上的数字是 b b ,就添加一条 a a 指向 b b 的有向边。

这张图有如下性质:

  1. 每个节点的入度为 1 1 ,出度为 1 1 ,整张图由若干个环组成(可能有自环)。
  2. 交换两个位置上的数等价于交换两个节点的出边指向,且:
    1. 如果交换前的两个节点在同一个环中,那么交换后环的个数一定多 1 1
    2. 如果交换前两个节点不在同一个环中,那么交换后环的个数一定少 1 1
  3. p i = i p_i=i 的排列构成的图由 n n 个自环组成,环的个数是 n n .
  4. 设图中有 l o o p s loops 个环,那么 f ( p ) = n l o o p s f(p)=n-loops ,即需要 n l o o p s n-loops 次操作将所有环都分解成自环。

由以上结论,可以知道最小交换次数就是 f ( p ) m |f(p)-m|

  1. 如果 f ( p ) < m f(p)<m ,需要合并 m f ( p ) m-f(p) 个环,找到前 m f ( p ) m-f(p) 个环的最小元素,依次合并即可。
  2. 如果 f ( p ) > m f(p)>m ,需要拆分 f ( p ) m f(p)-m 个环,从 1 1 开始遍历,如果不是自环,就将它与它所属的环中第二小的元素交换。

困死了,明天再写,这和字符串有什么关系啊。


使用集合维护每个环的点编号,果断超时。

仔细想想,合并环时,只需要知道前两个环的首个点编号。

拆分环时,只需要知道第一个至少有两个点的环的前两个点编号。

/* 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;
}
发布了375 篇原创文章 · 获赞 305 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/103173144
今日推荐