【正睿2019暑假集训】正睿892 蔡老板与宝藏

题目链接

设题目给出的排列为\(p[1\dots n]\),每个位置上数字的颜色为\(c[1\dots n]\)

对每个\(i\) (\(1\leq i\leq n\)),从\(i\)\(p[i]\)连边。根据排列的性质,可以得到若干个简单环。我们的最终目的,是要把这张“图”,变成只有自环。也就是“拆掉”所有长度大于\(1\)的环。

考虑一次操作能带来什么。

  • 如果被操作的两个点,在同一个环上,那么这个环被拆成两个环。特别地,如果这两个点相邻,那么入边的那个点,将变成一个自环,其他点保持原顺序,同时两个点交换颜色(别忘了颜色会交换!)。
  • 如果被操作的两个点,不在同一个环上,那么这两个点所在的两个环,将合并成同一个环。

在开始操作之前,我们把所有的环,按环里所有点是不是同一种颜色,分为“同色环”和“非同色环”两类。

对于一个非同色环,我们总能用 环长\(-1\) 次操作,将其拆掉(拆成若干个自环)。方法是,每次对环上相邻的两个点操作,相当于拆掉了其中一个点。用这种方法,先把环,变成所有相邻点都不同色(每个连续同色段,只保留一个点)。然后再按顺序操作一圈,就能把整个环拆掉。

对于一个同色环,直接拆是拆不掉的。考虑把它和别的环合并,变成一个非同色环。这里“别的环”的选择,就很重要。如果选另一个同色环与之合并(要求两个同色环,“同”的不能是同一种颜色),则相当于一次解决了两个同色环。而如果选择另一个非同色环与之合并(显然是一定能合并的),则只解决了当前这一个同色环。所以,优先让同色环与同色环合并,是比较好的。

把所有同色环,按颜色分类。每种颜色,对应了若干个环,称环的数量为这种颜色的“出现次数”。因为同色环合并,两个环的颜色必须不同,所以要配对尽可能多的同色环,可以采取如下的贪心策略:每次取出“出现次数”最多的两种颜色,从中各取一个环,将其合并。可以用一个大根堆(\(\texttt{priority_queue}\))来维护这个过程。

至此,我们可以用最少的操作次数,拆掉一个非同色环。并且可以贪心地,把所有同色环变成非同色环。于是本题圆满解决。时间复杂度\(O(n\log n)\)

值得一提的是,有且仅有一种情况是无解的:所有点颜色相同,且至少存在一个位置满足\(p[i]\neq i\)

参考代码:

//problem:ZR892
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int MAXN=1e5;
const int INF=1e9;
int n,a[MAXN+5],pos[MAXN+5],c[MAXN+5];
bool vis[MAXN+5];
vector<int>col_circles[MAXN+5];
vector<pii>ans;
inline void work(int i,int j){
	ans.pb(mk(i,j));
	swap(a[i],a[j]);
	pos[a[i]]=i;
	pos[a[j]]=j;
	swap(c[i],c[j]);
}
void solve(int st){
	//if(a[st] == st)return;
	assert(a[st] != st);
	vis[st]=1;
	int num = (c[a[st]] != c[st]);
	for(int i=a[st];i!=st;i=a[i]){
		vis[i]=1;
		num += (c[a[i]] != c[i]);
	}
	assert(num>0);
	int p=st;
	while(c[pos[p]] == c[p])
		p=pos[p];
	
	for(int i=1;i<=num;++i){
		while(c[pos[p]] == c[pos[pos[p]]]){
			int nxt=pos[p];
			work(pos[p],p);
			p=nxt;
		}
		p=pos[p];
	}
	for(int i=1;i<num;++i)
		work(p,a[p]);
}
int main() {
	int t;
	cin>>n>>t;
	for(int i=1;i<=n;++i){
		cin>>a[i];
		pos[a[i]]=i;
	}
	int max_c=-INF,min_c=INF;
	for(int i=1;i<=n;++i){
		cin>>c[i];
		ckmin(min_c,c[i]);
		ckmax(max_c,c[i]);
	}
	if(min_c == max_c){
		// 只有一种颜色
		bool fail=false;
		for(int i=1;i<=n;++i)
			if(a[i] != i){
				fail=true;
				break;
			}
		if(fail)
			cout<<-1<<endl;
		else
			cout<<0<<endl;
		return 0;
	}
	for(int i=1;i<=n;++i){ // 找环
		if(vis[i] || a[i]==i)
			continue;
		vis[i]=1;
		bool onecol=1;
		for(int j=a[i];j!=i;j=a[j]){
			vis[j]=1;
			onecol &= (c[j] == c[i]);
		}
		if(onecol){
			col_circles[c[i]].pb(i);
		}
	}
	priority_queue<pii>que;
	for(int i=1;i<=n;++i) // 枚举每一种颜色
		if(SZ(col_circles[i])){
			que.push(mk(SZ(col_circles[i]),i));
		}
	
	int lst=0;
	if(SZ(que) == 1){
		int cc=que.top().se;
		for(int i=1;i<=n;++i)
			if(c[i] != cc){
				lst=i;
				break;
			}
		assert(lst != 0);
	}
	
	while(!que.empty()){
		int c1 = que.top().se; que.pop();
		if(!SZ(que)){
			// c1是最后一个颜色
			// 只能和非同色环lst消
			while(SZ(col_circles[c1])){
				int p=col_circles[c1].back();
				work(p,lst);
				lst=p;
				col_circles[c1].pop_back();
			}
			break;
		}
		int c2 = que.top().se; que.pop();
		int p1 = col_circles[c1].back();
		int p2 = col_circles[c2].back();
		col_circles[c1].pop_back();
		col_circles[c2].pop_back();
		work(p1,p2);//两个颜色不同的"同色环",合并成了一个非同色环
		lst=p1;
		if(SZ(col_circles[c1]))
			que.push(mk(SZ(col_circles[c1]),c1));
		if(SZ(col_circles[c2]))
			que.push(mk(SZ(col_circles[c2]),c2));
	}
	for(int i=1;i<=n;++i)
		vis[i]=0;
	for(int i=1;i<=n;++i){
		if(vis[i] || a[i] == i)
			continue;
		solve(i);//把一个非同色环i消掉(拆成若干个自环)
	}
	cout<<SZ(ans)<<endl;
	for(int i=0;i<SZ(ans);++i)
		cout<<ans[i].fi<<" "<<ans[i].se<<endl;
	return 0;
}
posted @ 2020-07-14 19:46  duyiblue  阅读(331)  评论(0编辑  收藏  举报