1.题意
链接: 原题链接.
给你一个1~n全排列A,让你判断能否通过一种操作使之变为目标排列B。该操作是:交换与当前下标i相距
d
i
d_i
di的2个元素。
如:下标为5,
d
5
=
2
d_5=2
d5=2,那么可以将下标为5处的元素和下标为3或7处的元素交换。
2.思路
从图的角度思考,假如下标为a处可以到达下标为b处,那么就从a向b连一条有向边,我们发现交换是相互的,故可以把这条有向边改成无向边。
我们进一步发现想让下标为i的数变为
a
i
a_i
ai,而
a
i
a_i
ai在下标j,那么只要从i到j有路径就可以了。
那么是否只要对于每个下标i,其与目标排列的
a
i
a_i
ai所在下标j间连通,就一定存在一种合理交换方案得到目标排列?
答案是肯定的,构造方法类似于拓扑排序,在每个连通块中,先找到度为1的点,把对应元素移动过去,然后去掉唯一的一条边,循环进行,可得出一定有解。(一个连通块,按照topo序删点,剩下的模块依旧是一个连通块)
由于本题只需要判断真假,故只需要判断目标下标和原下标的连通性即可,这可以使用并查集或者floodfill算法,如下代码用的是后者。(啊,,比赛的时候并查集都忘了该咋写了~)
3.代码
#include <iostream>
using namespace std;
const int N=110;
bool g[N][N];
int n;
int a[N],d[N],con[N];
bool st[N];
int cnt;
void dfs(int u)
{
st[u]=true;
con[u]=cnt;
for(int i=0;i<n;i++)
{
if(g[u][i]&&!st[i])
{
dfs(i);
}
}
return;
}
void floodfill()
{
for(int i=0;i<n;i++)
{
if(!st[i])
{
cnt++;
dfs(i);
}
}
}
bool check()
{
for(int i=0;i<n;i++)if(con[a[i]-1]!=con[i])return false;
return true;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
for(int i=0;i<n;i++)cin>>d[i];
for(int i=0;i<n;i++)
{
if(d[i]+i<n)
{
g[i][i+d[i]]=1;
g[i+d[i]][i]=1;
}
if(i-d[i]>=0)
{
g[i][i-d[i]]=1;
g[i-d[i]][i]=1;
}
}
floodfill();
if(check())cout<<"YES\n";
else cout<<"NO\n";
}
4.收获
- 当遇到比较复杂的题目,考虑用图论的角度思考问题
- 拓扑序的优秀性质——一个连通块,按照topo序删点,剩下的模块依旧是一个连通块。诶呦,不错哟~