對於一個的排列,定義爲最少所需的交換次數(不要求相鄰),使得。
給定長爲的排列和,問最少需要幾次交換,可以使得.
輸出字典序最小的方案。
按如下方法建立個節點的有向圖:如果位置上的數字是,就添加一條指向的有向邊。
這張圖有如下性質:
- 每個節點的入度爲,出度爲,整張圖由若干個環組成(可能有自環)。
- 交換兩個位置上的數等價於交換兩個節點的出邊指向,且:
- 如果交換前的兩個節點在同一個環中,那麼交換後環的個數一定多。
- 如果交換前兩個節點不在同一個環中,那麼交換後環的個數一定少。
- 的排列構成的圖由個自環組成,環的個數是.
- 設圖中有個環,那麼,即需要次操作將所有環都分解成自環。
由以上結論,可以知道最小交換次數就是。
- 如果,需要合併個環,找到前個環的最小元素,依次合併即可。
- 如果 ,需要拆分個環,從開始遍歷,如果不是自環,就將它與它所屬的環中第二小的元素交換。
困死了,明天再寫,這和字符串有什麼關係啊。
使用集合維護每個環的點編號,果斷超時。
仔細想想,合併環時,只需要知道前兩個環的首個點編號。
拆分環時,只需要知道第一個至少有兩個點的環的前兩個點編號。
/* 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;
}