Permutation Shift解题报告

发布于 2021-11-16  1078 次阅读


链接:https://codeforces.com/problemset/problem/1553/E

这道题先破例先上一波代码

#include <bits/stdc++.h>
using namespace std;
int n,m,p[355000];
bool check(int index){
	vector<int>v;
	v.push_back(0);
	for(int i=index+1;i<=n;i++) v.push_back(p[i]);
	for(int i=1;i<=index;i++) v.push_back(p[i]);
	int tot=0;
	map<int,int>nxt;
	map<int,bool>used;
	for(int i=1;i<=n;i++){
		nxt[i]=v[i];
	}
	for(int i=1;i<=n;i++){
		if(used[i]) continue;
		tot++;
		int now=nxt[i];
		while(!used[now]){
			used[now]=1;now=nxt[now];
		}
	}
	return n-tot<=m;
}
void solve(){
	map<int,int>cnt;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>p[i];
		cnt[(i-p[i]+n)%n]++;
	}
	vector<int>ans;
	for(int i=0;i<n;i++){
		if(n-2*m<=cnt[i]&&check(i)) ans.push_back(i);
	}
	cout<<ans.size();
	for(auto i:ans) cout<<" "<<i;
	cout<<endl;
}
int main(){
	int T;
	cin>>T;
	while(T--)
		solve();
	return 0;
}

诶,看着很像n2复杂度对不对。实则它只是一个n的复杂度,下面来详细说明

先说说解题思路,关于这种shift问题有一个很好的表示方法

如以下数列

[4,5,6,1,2,3],然后对每个数求其(aindex-index+n)%n,最终可发现没个数最后算出的结果都是一样,所以我们可以通过这一性质来容易地表示出shift了几个。

然后下一个性质经过m次两两交换,最多可使得2*m个数字是凌乱的,也就是说至少还有n-2*m个数字还放置它应该所处的位置上

那么,我们可以去枚举shift了几个数字,然后将其反向的shift回去,最后判断其是否可以交换会1 2 3 4 5.....n的顺序,这个判断方式简单的说一下代码如下

bool check(int index){
	vector<int>v;
	v.push_back(0);
	for(int i=index+1;i<=n;i++) v.push_back(p[i]);
	for(int i=1;i<=index;i++) v.push_back(p[i]);
	int tot=0;
	map<int,int>nxt;
	map<int,bool>used;
	for(int i=1;i<=n;i++){
		nxt[i]=v[i];
	}
	for(int i=1;i<=n;i++){
		if(used[i]) continue;
		tot++;
		int now=nxt[i];
		while(!used[now]){
			used[now]=1;now=nxt[now];
		}
	}
	return n-tot<=m;
}

v数组是最后shift回去之后的顺序,然后建立一张图每个i都指向vi,代表着说目前放在i的数字应该移动到vi上面去,最后建出来的图每个节点都至多连出一条边,也至多连入一条边,并且连边的点都在环内,假设这个环的尺寸为k,如果想要把这个环内所有数字都正常归位,则需要至少k-1次交换,最后需要总交换就是n-tot(tot是建出来的图里连通块的数量),最后判断其是否不大于m即可

那还是回归到刚才那个问题,这又枚举又遍历的复杂度不高吗?其实还真就不高,注意之前提到过的性质:经过m次两两交换,最多可使得2*m个数字是凌乱的,也就是说至少还有n-2*m个数字还放置它应该所处的位置上。也就是说当前所枚举的的cntshift必须要不小于n-2*m才有可能是我们最后要找的答案,而题目中数据有个看似奇怪的约束0≤m≤n/3,也就是说n-2*m最小不过n/3,而cnti(0<=i<n)最后总和等于n,也就是说最后附和条件的cntshift不过最多就是3个而已,所以最后输出的可能数字数量也不过最多三个而已,详见以下代码

for(int i=1;i<=n;i++){
		cin>>p[i];
		cnt[(i-p[i]+n)%n]++;
	}
	vector<int>ans;
	for(int i=0;i<n;i++){
		if(n-2*m<=cnt[i]&&check(i)) ans.push_back(i);
	}


回忆这理想不够理想,沿途逛世间一趟只有向上