对于一个 的排列 ,定义 为最少所需的交换次数(不要求相邻),使得 。
给定长为 的排列 和 ,问最少需要几次交换,可以使得 .
输出字典序最小的方案。
按如下方法建立 个节点的有向图:如果 位置上的数字是 ,就添加一条 指向 的有向边。
这张图有如下性质:
- 每个节点的入度为 ,出度为 ,整张图由若干个环组成(可能有自环)。
- 交换两个位置上的数等价于交换两个节点的出边指向,且:
- 如果交换前的两个节点在同一个环中,那么交换后环的个数一定多 。
- 如果交换前两个节点不在同一个环中,那么交换后环的个数一定少 。
- 的排列构成的图由 个自环组成,环的个数是 .
- 设图中有 个环,那么 ,即需要 次操作将所有环都分解成自环。
由以上结论,可以知道最小交换次数就是 。
- 如果 ,需要合并 个环,找到前 个环的最小元素,依次合并即可。
- 如果 ,需要拆分 个环,从 开始遍历,如果不是自环,就将它与它所属的环中第二小的元素交换。
困死了,明天再写,这和字符串有什么关系啊。
使用集合维护每个环的点编号,果断超时。
仔细想想,合并环时,只需要知道前两个环的首个点编号。
拆分环时,只需要知道第一个至少有两个点的环的前两个点编号。
/* 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;
}