CF1867D Cyclic Operations

发布时间 2023-09-12 12:03:30作者: One_JuRuo

前言

赛时没调出来,赛后调了一个上午,最后发现是有个地方没清零。

思路

首先对于位置 \(i\),我们必须要保证进行的操作中,最后一次出现 \(i\)\(i\) 的后面一定是 \(a_i\)

那么我们考虑统计所有位置上的要求,用有向边链接,那么就会出现一个有环有向图(一定有环,因为点数等于边数)。

那么,自然想到缩点。

首先,如果有某个环的长度不为 \(k\)(不讨论长度为 \(1\) 的情况),那么一定无解,因为总会剩几个点没办法改,或者为了改最后几个点而导致把最开始的点改了,自己手玩一下应该很好理解。

那么还有不构成环的,也可以理解成长度为 \(1\) 的情况,我们可以先满足这些边的要求,这样可能会使得环内得答案不正确,但是我们可以最后再处理环,把之前错误的答案改正确。

所以做法就出来了,先建图,然后 tarjan 缩点,统计每个环的长度,长度要么为 \(k\),要么为 \(1\),如果存在不是 \(k\) 也不是 \(1\) 肯定无解,然后再统计长度为 \(k\) 的个数,如果没有也肯定无解,否则就是有解。

另外,还有 \(k=1\) 的特殊情况,这种情况,必须满足 \(a_i=i\),也就是它只能每次选一个值 \(l\),那必定是把 \(a_l\) 改成 \(l\),所以必须满足上述条件。

\(k\neq1\) 则必须满足 \(a_i\neq i\),因为 \(l\) 互不相同。

AC code

#include<bits/stdc++.h>
using namespace std;
int T,n,k,a[100005],e[100005],ne[100005],h[100005],num,idx=1,dcnt,siz[100005],dfn[100005],low[100005],z[100005],top,in_z[100005],ff,flag;
inline void add(int a,int b){e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
void dfs(int u)
{
	dfn[u]=low[u]=++dcnt,in_z[u]=1,z[++top]=u;
	for(int i=h[u];i;i=ne[i])
	{
		if(!dfn[e[i]]) dfs(e[i]),low[u]=min(low[u],low[e[i]]);
		else if(in_z[e[i]]) low[u]=min(low[u],dfn[e[i]]);
	}
	if(dfn[u]==low[u])
	{
		++num;
		while(z[top+1]!=u) in_z[z[top--]]=0,++siz[num];//因为这个写法,可能和上一次的进栈的元素相同,导致错误,所以要初始化z数组。
	}
}
inline bool sol()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n+1;++i) z[i]=siz[i]=h[i]=dfn[i]=0;//因为判断是z[top+1],所以要多预处理一位
   //奇怪的是,我在洛谷这么写都没错,难道是洛谷没卡这种情况?
	idx=1,num=flag=dcnt=top=0;
	if(k==1)
	{
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		for(int i=1;i<=n;++i) if(i!=a[i]) return 0;
		return 1;
	}
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) if(i==a[i]) return 0;else add(i,a[i]);
	for(int i=1;i<=n;++i) if(!dfn[i]) dfs(i);
	for(int i=1;i<=num;++i)
	{
		if(siz[i]!=1&&siz[i]!=k) return 0;
		if(siz[i]==k) flag=1;
	}
	return flag;
}
int main()
{
	scanf("%d",&T);
	while(T--)
		if(sol()) puts("YES");
		else puts("NO");
	return 0;
}