洛谷 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;
}
posted @ 2020-06-30 10:48  ycx060617  阅读(486)  评论(0编辑  收藏  举报