洛谷 P6305 - 循环排序
做完eJOI填。
UPD 2020.7.24:填坑。
洛谷题目页面传送门
给定长度为\(n\)的序列\(a\)和常数\(m\)。每次操作可以选择\(k\)个下标\(x_i\),按\(x_1\to x_2\to \cdots\to x_k\to x_1\)轮换元素。你需要在\(\sum k\leq m\)的前提下最小化操作次数,使得\(a\)不降。如果有解,输出操作方案;否则报告无解。
\(n\in\left[1,2\times10^5\right],m\in\left[0,2\times10^5\right],a_i\in\left[1,10^9\right]\)。
首先注意到数们的具体大小不重要,于是离散化到值域\([1,s](s\in[1,n])\)上。
不难发现,最终不降的\(a\)的长相是固定的,一定是形如\(a'=1,1,\cdots,1,2,2,\cdots,2,\cdots,s,s,\cdots,s\)的。而现实是残酷的,一开始的\(a\)会是上面那个数列\(a'\)的一个重新排列的结果。于是想到图论。
\(\forall i\in[1,n]\),连有向边\((a'_i,a_i)\)。注意到,它的一些性质是排列(每个数只出现一次)建出来的图的一些性质的弱化。事实上,排列是它的一个特例。它满足所有点的入出度相等(在\(a'\)中出现过多少次,在\(a\)中就出现多少次),而排列满足所有点的入出度都为\(1\)。这等价于,排列建出来的图是由若干个简单环组成的,而它是由若干个极大基图连通子图的欧拉回路组成的(根据欧拉回路的判定,正确性显然)。
考虑对于每个欧拉回路,都进行一次操作(就可以恢复成\(a'\)中的那部分)。跑欧拉回路的时候,记录的是每条有向边\((a'_i,a_i)\)中的\(i\)。特殊地,对于大小为\(1\)的欧拉回路,不进行操作。这样的话,\(\sum k\)达到了下限,如果还\(>m\)的话就没救了。
接下来发现这样不是最优解。有一种策略,就是对若干个极大基图连通子图中的各一个元素操作,将它们联系到一起,成为一个极大基图连通子图。若选了\(x\)个,那么操作数量能够减少\(x-2\),但是换来的是\(\sum k\)会增加\(x\)。于是在符合条件的情况下最大化\(x\)即可。
时间复杂度\(\mathrm O(n\log n)\)(离散化)。
代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=200000;
int n,m;
int a[N+1];
vector<int> nums;
void discrete(){//离散化
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=n;i++)a[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin()+1;
}
int pos_to[N+1];
vector<pair<int,int> > nei[N+1];
vector<bool> ban[N+1];
vector<int> pa;
vector<vector<int> > ans;
bool vis[N+1];
int now[N+1];
void dfs(int x){
vis[x]=true;
for(int &i=now[x];i<nei[x].size();i++)if(!ban[x][i]){
int y=nei[x][i].X,y0=nei[x][i].Y;
ban[x][i]=true;
dfs(y);
pa.pb(y0);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)scanf("%d",a+i),nums.pb(a[i]);
discrete();
for(int i=1;i<=n;i++)pos_to[i]=a[i];
sort(pos_to+1,pos_to+n+1);//此时pos_to=a'
int cnt=0;
for(int i=1;i<=n;i++)if(pos_to[i]!=a[i])cnt++,nei[pos_to[i]].pb(mp(a[i],i)),ban[pos_to[i]].pb(false);//建图
if(cnt>m)return puts("-1"),0;//报告无解
for(int i=1;i<=n;i++)if(!vis[i]&&nei[i].size())pa.clear(),dfs(i),ans.pb(pa);//跑欧拉回路
if(min(int(ans.size()),m-cnt)>1){//进行那种策略
pa.clear();
for(int i=0;i+1<min(int(ans.size()),m-cnt);i++)swap(a[ans[i][0]],a[ans[i+1][0]]);
for(int i=0;i<min(int(ans.size()),m-cnt);i++)pa.pb(ans[i][0]);
ans.clear();ans.pb(pa);
}
else ans.clear();
for(int i=1;i<=n;i++)nei[i].clear(),ban[i].clear(),vis[i]=now[i]=0;
for(int i=1;i<=n;i++)if(pos_to[i]!=a[i])nei[pos_to[i]].pb(mp(a[i],i)),ban[pos_to[i]].pb(false);//重新跑一遍
for(int i=1;i<=n;i++)if(!vis[i]&&nei[i].size())pa.clear(),dfs(i),ans.pb(pa);
cout<<ans.size()<<"\n";
for(int i=0;i<ans.size();i++){//输出答案
printf("%d\n",int(ans[i].size()));
for(int j=ans[i].size()-1;~j;j--)printf("%d ",ans[i][j]);
puts("");
}
return 0;
}