Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

Atcoder 题面传送门 & 洛谷题面传送门

震惊,我竟然能独立切掉 AGC E 难度的思维题!

hb:nb tea 一道

感觉此题就是找性质,找性质,再找性质(

首先看到排列有关的问题,我们可以很自然地将排列拆成一个个置换环,即我们建一张图 \(G\),对于 \(i\in[1,n]\) 连边 \(i\to p_i\),那么题目的要求就可以转化为:对于每个点 \(i\),它置换环上下一步或者下下步为 \(a_i\)。

做出这个简单的转化后,就可以发现一个非常 trivial 的性质:

Observation \(1\). 如果存在三个及以上的 \(i\) 满足 \(a_i=x\),那么答案肯定是 \(0\)

证明显然,因为只有 \(x\) 上一个或者上上个才能在两步之内到达 \(i\)。

这给我们一个启发,我们可以建一张新的图 \(G'\),对于每个 \(i\) 连边 \(i\to a_i\),那么这个图满足每个点的入度 \(\le 2\),每个点的出度为 \(1\)。我们还可以发现,\(\forall i\),\(i,a_i\) 在 \(G\) 中肯定是在同一个置换环内的,因此 \(G'\) 连通块的情况和 \(G\) 连通块的情况是大体相同的。因此我们可以观察得出性质:

Observation \(2\). 对于某个 \(G\) 中某个环 \(C\),除了 \(|C|\) 为偶数并且并且每个 \(a_i\) 都是 \(i\) 向后两步的位置的情况除外,其他情况 \(C\) 中的元素在 \(G'\) 的基图中依然连通。

说人话,就是对于绝大部分情况,原来在同一个连通块中的元素后来依然在一个连通块中,唯一一个例外是如果置换环的大小 \(siz\) 为偶数,并且每个 \(a_i\) 都是 \(i\) 向后两步的位置,这种情况原来的置换环会分裂为两个大小为 \(\dfrac{siz}{2}\) 的置换环。

简单证明一下,对于一个置换环而言,如果存在某个 \(i\) 满足 \(a_i\) 是 \(i\) 的下一步位置,那么如果置换环大小为 \(2\) 结论显然成立,否则设 \(j\) 为 \(i\) 上一个位置,那么 \(a_j\) 必然是 \(i\) 或 \(a_i\) 中的一个,那么我们就可以将 \(j\) 加入 \(i\) 所在的连通块,再考虑 \(j\) 的上一个位置 \(k\),\(a_k\) 必然也是 \(i\) 或 \(j\) 中的一个,如此归纳下去即可得证。也就是说置换环分裂的情况只可能是每个 \(a_i\) 都是 \(i\) 往后两格的位置,而如果置换环大小为奇数,简单画张图即可发现,最后建出来的必然还是一个与原置换环大小相同的环。得证。


这又给我一个启发:不妨来观察一下 \(G'\) 中每个连通块的形态。我又画了几张图,发现了以下性质:

Observation \(3\). \(G'\) 一定是一个基环内向森林,并且每个不在基环树上的点都形成一条条链

证明不会

Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

我们考虑再来看一下这个环和这些链有啥性质,以上图为例,按照之前的推论,\(G'\) 的每个连通块一定是由一个环(我们称之为主环),和一些以主环上某个点为结尾的链(如果某条链以环上某个点 \(x\) 结尾,那么我们就称这条链是挂在 \(x\) 上的),我们假设挂在 \(i\) 上的链的长度为 \(b_i\)——我们显然在 BFS 求出每个连通块的环的同时求出 \(b_i\)。

那么问题就来了,\(G'\) 里面这个主环和原来的置换环有什么关系呢?

方便起见,我们将主环上的点按原来置换环上的顺序依次编号 \(c_1,c_2,\cdots,c_k\)。

Obseration \(4\). 主环一定是由 \(c_1\) 沿着置换环每次跳 \(1\) 格或两格,跳 \(k\) 步之后形成的。

证明显然,因为置换环上相邻两个点 \(c_i,c_{i+1}\) 必然满足 \(c_{i+1}\) 是 \(c_i\) 下一个节点,或者 \(c_{i+1}\) 是 \(c_i\) 下下个节点,所以每次必然是跳 \(1\) 步或 \(2\) 步。

知道了这个性质之后,我们就有对于 \(k<\) 置换环大小 \(|C|\) 的情况,跳 \(k\) 步跳的总长度必然在 \(k\) 和 \(2k\) 之间,又因为它最后回到了原点,因此总长度必然是 \(|C|\) 的整数倍,而 \(2k<2|C|\),因此跳的总长度只可能是 \(|C|\),这样一来我们可以将连通块分为三类:

  • \(2k<|C|\),此时就算每步跳 \(2\) 格也达不到一周,总方案数就是 \(0\)。

  • \(|C|<2k<2|C|\),这种情况比较复杂我们过会儿讨论。

  • \(2k=2|C|\),即 \(k=|C|\),此时该连通块就是一个环,那么我们记 \(num_i\) 为有多少个这样的大小为 \(i\) 的连通块,那么对于每个环而言,它在原排列 \(p\) 中有三种可能:

    • 保持不变,即 \(p\) 中也存在一个和该置换环一模一样的置换环
    • 如果 \(i>1\) 且为奇数,那么存在一个长度等于 \(i\) 的置换环,满足从环上某个点开始每次跳两步得到的环与原连通块相同。
    • 和另一个大小为 \(i\) 的置换环拼成一个长度为 \(2i\) 的置换环,假设我们已经选出了这两个置换环 \(C_1,C_2\),那么我们固定住 \(C_1\) 的某个位置,置换环 \(C_1\) 放置的位置就确定了,此时 \(C_2\) 可以随意旋转,有 \(i\) 种可能。举个栗子,置换环 \((1,2,3,4)\) 和 \((5,6,7,8)\) 拼在一起有 \((1,5,2,6,3,7,4,8),(1,6,2,7,3,8,4,5),(1,7,2,8,3,5,4,6),(1,8,2,5,3,6,4,7)\) 四种可能(梦回 P4709?)

    因此考虑枚举有多少对长度为 \(i\) 的置换环拼在一起,记这个数为 \(j\),那么选出这 \(j\) 对置换环的方案数为 \(\dfrac{1}{j!}\prod\limits_{k=0}^{j-1}\dbinom{num_i-2k}{2}\),这 \(j\) 对置换环拼在一起的方案数为 \(i^j\),安放剩余置换环的方案数为 \(t^{num_i-2j}\),其中 \(t\) 表示每个置换环在原排列中的方案数,如果 \(i>1\) 且 \(i\) 为奇数那么 \(t=2\),否则 \(t=1\),因此总贡献为 \(i^j\times t^{num_i-2j}\dfrac{1}{j!}\prod\limits_{k=0}^{j-1}\dbinom{num_i-2k}{2}\),这个东西显然可以在枚举 \(j\) 的同时维护,时间复杂度 \(\mathcal O(n\log n)\) 或 \(\mathcal O(n)\),取决于你的心情


到这里我们已经解决了本题的大部分情况,还剩最棘手的一种情况没有解决——那就是 \(|C|<2k<2|C|\),即图是一个纯正的基环树(大雾)的情况。

考虑这些链是哪里来的,观察一会儿可以发现:

Observation \(5\). 对于一个主环上的点 \(c_x\) 引出的长度为 \(len\) 的链,它在原置换环上有以下两种情况(其中蓝线为 \(G'\) 中的边,绿边为 \(G\) 中的边(注:下图中的 \(C_{x-len+1}\) 应为 \(C_{x-len+2}\),\(C_{x-len},C_{x-len-1}\) 也同理应该变为 \(C_{x-len+1},C_{x-len}\),传上去之后才发现有问题,懒得改了/cy):

Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

Atcoder Grand Contest 008 E - Next or Nextnext(乱搞+找性质)

说人话用数学语言来表述,也就是说假设这条链上 \(len\) 个点为 \(p_1,p_2,p_3,\cdots,p_{len}\),那么有以下可能:

  • \(p_1\) 夹在 \(c_x,c_{x-1}\) 中间,\(p_2\) 夹在 \(c_{x-1},c_{x-2}\) 中间,……,\(p_{len}\) 夹在 \(c_{x-len+1},c_{x-len}\) 中间
  • \(c_x,c_{x-1}\) 中间没有点,\(p_1\) 夹在 \(c_{x-1},c_{x-2}\) 中间,\(p_2\) 夹在 \(c_{x-2},c_{x-3}\) 中间,……,\(p_{len}\) 夹在 \(c_{x-len},c_{x-len-1}\) 中间

我们给 \(c_i,c_{i-1}\) 中间边的状态(中间夹了多少个点)量化为一个数组 \(e_i\),那么显然 \(e_i\le 2\),否则 \(c_i\) 就不是 \(c_{i-1}\) 的下个点或下下个点了,此时以下两种排列的方案就可以表述为:

  • 令 \(e_x,e_{x-1},e_{x-len+1}\) 加 \(1\)
  • 强制令 \(e_x=0\),并令 \(e_{x-1},e_{x-2},\cdots,e_{x-len}\) 加 \(1\)

不难发现这相当于一个环上的区间覆盖问题,第一种方案可以视作覆盖了区间 \([x-len+1,x]\),第二种方案可视作覆盖了区间 \([x-len,x]\),每个点覆盖的区间不能相交。

这样还是不太好直接下手,因为它是一个环,直接 \(dp\) 显然会出现后效性。不过我们可以很自然地想到断环成链,即找到环上第一个 \(b_i\ne 0\) 的点 \(ps\),枚举 \(ps\) 覆盖的区间(\([ps-b_{ps}+1,ps]\) or \([ps-b_{ps},ps]\)),这样就可以视作链上的 \(dp\) 了,\(dp_i\) 表述考虑链上 \(ps\sim i\) 的点的放置方案,转移就实时维护上一个 \(b_j\ne 0\) 的点 \(j\),分情况讨论:

  • \(j\ge i-b_i+1\),\(dp_j=0\),因为无论覆盖 \([i-b_i+1,i]\) 还是 \([i-b_i,i]\) 都会与上一个区间有交
  • \(j=i-b_i\),\(dp_j=dp_i\),因为只能覆盖 \([i-b_i+1]\)
  • \(j<i-b_i\),\(dp_j=2dp_i\)

具体实现时,不需真的建出 \(dp\) 数组,由于 \(dp_i\) 只与上一个 \(j\) 的 \(dp\) 值有关,因此可以维护一个变量表示上一个位置的 \(dp\) 值,复杂度 \(\mathcal O(\text{环的大小})\),而由于 \(\sum\text{环的大小}\) 为 \(\mathcal O(n)\),因此这部分复杂度是 linear 的。


于是这题就真的做完了,复杂度 \(n\log n\) or \(\mathcal O(n)\)

细节多得一批……程序下面放了两组 hack 数据,是我在与 std 对拍过程中曾经错过的数据。

const int MAXN=1e5;
const int MOD=1e9+7;
const int INV2=5e8+4;
int qpow(int x,int e){
	int ret=1;
	for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
	return ret;
}
int n,a[MAXN+5],in[MAXN+5],deg[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
	for(int i=(ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int bel[MAXN+5],comp=0,siz[MAXN+5],cyc[MAXN+5],b[MAXN+5];
bool vis[MAXN+5];
void dfscomp(int x){
	if(bel[x]) return;bel[x]=comp;
	for(int e=hd[x];e;e=nxt[e]) dfscomp(to[e]);
}
vector<int> c[MAXN+5];
void findcyc(int x){
	if(vis[x]) return;c[bel[x]].pb(x);
	vis[x]=1;findcyc(a[x]);
}
int num[MAXN+5],way[MAXN+5];
int main(){
	scanf("%d",&n);init_fac(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),in[a[i]]++;
	for(int i=1;i<=n;i++) deg[i]=in[i];
	for(int i=1;i<=n;i++) adde(i,a[i]),adde(a[i],i);
	for(int i=1;i<=n;i++) if(in[i]>=3) return puts("0"),0;
	for(int i=1;i<=n;i++) if(!bel[i]) comp++,dfscomp(i);
	for(int i=1;i<=n;i++) siz[bel[i]]++,b[i]=1;
	queue<int> q;
	for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
	while(!q.empty()){
		int x=q.front();q.pop();vis[x]=1;
		if(!--in[a[x]]) q.push(a[x]),b[a[x]]+=b[x];
	}
	for(int i=1;i<=n;i++) if(vis[i]&&deg[i]==2) return puts("0")&0;
	for(int i=1;i<=n;i++) if(!vis[a[i]]&&vis[i]) b[a[i]]+=b[i];
	for(int i=1;i<=n;i++) b[i]--;
	for(int i=1;i<=n;i++) if(!vis[i]) findcyc(i);
	for(int i=1;i<=comp;i++) cyc[i]=c[i].size();
	int ans=1;
	for(int i=1;i<=comp;i++){
		if(siz[i]==cyc[i]){num[siz[i]]++;continue;}
		if(siz[i]==2){way[i]=1;continue;}
		if((cyc[i]<<1)<siz[i]) return puts("0")&0;
		int ps=-1,len=-1;
		for(int j=0;j<c[i].size();j++){
			int x=c[i][j];
			if(b[x]){ps=j;len=b[x];break;}
		} assert(~ps);
		int pre=ps,pre_dp=1;
		for(int j=ps+1;j<c[i].size()+ps;j++){
			int id=c[i][j%c[i].size()];
			if(b[id]){
				int cur_dp=0;
				if(j<ps-len+1+c[i].size()){
					if(j-b[id]+1>pre) cur_dp=pre_dp;
					if(j-b[id]>pre) cur_dp=(cur_dp+pre_dp)%MOD;
				} pre_dp=cur_dp;pre=j;
			}
		} way[i]=(way[i]+pre_dp)%MOD;
		if(len!=cyc[i]){
			pre=ps;pre_dp=1;
			for(int j=ps+1;j<c[i].size()+ps;j++){
				int id=c[i][j%c[i].size()];
				if(b[id]){
					int cur_dp=0;
					if(j<ps-len+c[i].size()){
						if(j-b[id]+1>pre) cur_dp=pre_dp;
						if(j-b[id]>pre) cur_dp=(cur_dp+pre_dp)%MOD;
					} pre_dp=cur_dp;pre=j;
				}
			} way[i]=(way[i]+pre_dp)%MOD;
		} ans=1ll*ans*way[i]%MOD;
	}
	for(int i=1;i<=n;i++){
		int mul=1,sum=0;
		for(int j=0;(j<<1)<=num[i];j++){
			sum=(sum+1ll*mul*qpow(((i&1)&&(i^1))?2:1,num[i]-(j<<1))%MOD*ifac[j])%MOD;
			mul=1ll*mul*(num[i]-(j<<1))%MOD*(num[i]-(j<<1)-1)%MOD*INV2%MOD*i%MOD;
		} ans=1ll*ans*sum%MOD;
	} printf("%d\n",ans);
	return 0;
}
/*
6
2 3 1 1 4 4
*/
/*
6
2 3 1 1 4 5
*/
上一篇:COCI2015/2016 Contest#4 D


下一篇:OI迷惑行为大赏