【题解】P8210 [THUPC2022 初赛] 造计算机
这题本人曾经在去年 6 月搬到过联考,而且是在最小化
首先特判所有数已经归位的情况,这时
以下的「Solution 1」为本题做法(你可以在 Matrix 67 的博客 里面找到本题),「Solution 2」为最小化
该问题最早出现于 Futurama 第 6 季第 10 集 The Prisoner of Benda。(虽然出题人并没有看过)
下面将交换
Solution 1
原动画中给出了如下的构造解:
将排列
设
Solution 2
这篇论文 给出了最优构造(如果你想要阅读原论文的话,注意此处的操作是自左向右执行的,而论文中是从右向左执行的):
考虑将原排列分解为多个非空的环
以
-
G_1(x)=(a_1x)\to (a_2x)\to \cdots\to (a_kx) -
F_1(x)=(a_1x)
进行操作:
设
其正确性容易验证。我们下面证明这个解是最优的。
证明
首先我们只保留环中的元素,那些不用换位置的元素我们忽略。
假设
另一方面,对于每个元素,它的初始位置与最终位置不同,一定至少被交换过一次。因此
进一步地,考虑第一个环
综上,
类似地,对于每个环,我们考虑环中第一次被操作到的元素,它同样会被交换了两次。于是对于每个环,它首次操作到的元素和最后一次被操作到的元素是同一个。
总结一下,对于环
- 有且仅有唯一一个元素
a\in C_1 ,满足有两次关于a 的操作; - 对于其他所有
C_1 中的元素,其操作时间在a 的这两次操作之间。
其余环也有类似性质。
现在对于每个环
不失一般性地,我们:
- 通过给环编号,可以使得
N_1\leq N_i,\forall 1<i \leq r ; - 通过交换
x,y ,可以使得(ay) 在(ax) 之前; - 通过改变第一个环的起点,可以使得
a 为C_1 的最后一个元素(a=a_k )。
现在考虑
假设
M 中只包含C_1 中的元素。由于我们需要对于i=1,2,\cdots k-1 把a_{i+1} 移到a_i ,以下操作必须按照如下顺序执行:(a_ky),(a_{k-1}y),\cdots (a_1y) 此时
a_1 到达不了a_k ,矛盾。
现在假设
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
int getint(){ int x;scanf("%d",&x);return x; }
int a[N];
bool vis[N];
vector<int>r[N];int cnt=0;
vector<pair<int,int> >ans;
void swp(int x,int y){
ans.emplace_back(min(x,y),max(x,y));
swap(a[x],a[y]);
}
int main(){
int n=getint();
for(int i=1;i<=n;i++)a[i]=getint();
a[n+1]=n+1;a[n+2]=n+2;
for(int i=1;i<=n;i++){
if(!vis[i]){
int u=i;
while(!vis[u]){
vis[u]=1;
r[cnt].push_back(u);
u=a[u];
}
// reverse(r[cnt].begin(),r[cnt].end());
++cnt;
}
}
vector<int>cy;
for(int i=0;i<cnt;i++)if(r[i].size()>1)cy.push_back(i);
if(cy.empty())return puts("0 0"),0;
for(int i=cy.size()-1;i;--i)swp(r[cy[i]].back(),n+2);
swp(r[cy[0]].back(),n+1);
for(int i:r[cy[0]])swp(i,n+2);
swp(r[cy[0]].front(),n+1);
for(int i=1;i<cy.size();i++)for(int j:r[cy[i]])swp(j,n+1);
swp(n+1,n+2);
printf("%d %d\n",2,(int)ans.size());
for(auto i:ans)
printf("%d %d\n",i.first,i.second);
// for(int i=1;i<=n+2;i++)cerr<<"> "<<a[i];cerr<<endl;
return 0;
}