题目背景
Alice和Bob玩游戏
题目描述
Alice给出一个n的全排列表示一个函数f,即给出的第i个数字即为f(i)。
现在Bob需要给出一个字典序尽可能小的函数g,使得对于任意i,f(g(i))=g(f(i))
输入输出格式
输入格式:
第一行一个整数n
第二行n个整数,依次表示f(1)、f(2)...f(n)
输出格式:
共一行,n个整数,依次表示g(1)、g(2)...g(n)
很绕的一个题...
分析一下发现是会按循环的规律来填的。
如果g[i]=j,那么设g[f[x]]=j那么x是唯一的,因为f是个单射函数。假设我们求得了x,那么又有f[g[x]]=g[f[x]]=j。又因为f是单射函数,所以g[x]又被确定了。这样就有循环了
那么题目又是让我们求字典序最小,所以肯定是从小到大的试填,填了g[i]之后我们又可以继续根据找到上面的规律继续填下去。如果你不按这个规律填肯定就出错了。如果填到最后发现有循环了,也就是这个位置之前被填过了,而且是我们将要填的这个数,就说明这个循环是可行的了。不然的话这个循环就和之前填的是有冲突的了,我们就要试下一个数字了。
解决求x是很简单的,我们只要在一开始读入是时候预处理一下就行了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<algorithm>
#include<cmath>
#define rg register
#define il inline
using namespace std;
typedef unsigned long long ll;
ll read(){
ll ans=0,flag=1;char ch;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
ans=ch^48;
while((ch=getchar())>='0'&&ch<='9') ans=(ans<<3)+(ans<<1)+(ch^48);
return flag*ans;
}
void write(ll x){
if(x<0){putchar('-');x=-x;}
if(x>9) write(x/10);
putchar(x%10+'0');
}
int f[800000+5];
int g[800000+5];
bool cant;//标记是否冲突
void dfs(int now,int k){//now是x,k是要往这里填的数。
if(g[now]&&g[now]!=k){cant=true;return;}//如果填到这里发现之前被填过了而且不是我们想要填的,就是冲突了。
if(g[now]==k){cant=false;return;}//这个循环是可行的
g[now]=k;
dfs(f[now],f[k]);按找到的规律继续天下去。
if(cant) g[now]=0;//回溯的时候如果不行就清零
}
int main(){
int n=read();
for(rg int i=1;i<=n;i++) f[read()]=i;
for(rg int i=1;i<=n;i++){//g[1]到g[n]
if(g[i]) continue;//如果之前填好了就不用填了
cant=false;
for(rg int j=1;j<=n;j++){//从1开始试填。
dfs(i,j);
if(!cant)break;//填成功了就填下个
}
}
for(rg int i=1;i<n+1;i++){
write(g[i]);putchar(' ');
}
return 0;
}