【正睿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;
}