zoukankan      html  css  js  c++  java
  • @atcoder


    @description@

    给定一个长度为 N 的序列 a,问有多少排列 p,满足对于每一个 i,都有 (a_i = p_i)(a_i = p_{p_i}) 成立。

    原题传送门。

    @solution@

    为了更直观地理解问题,不妨建个图:连有向边 (i -> p_i)
    对于任意序列 a,这样建出的图是一个基环树森林;对于排列 p,这样建出的图是若干个环。

    (a_i = p_i)(a_i = p_{p_i}) 成立时,相当于我们把排列 p 对应的图的某个边 (i -> p_i) 替换成 (i -> p_{p_i}),最终得到序列 a 对应的图。

    如果将某一个奇环的边全部替换,将得到另一个大小相同的奇环。
    如果将某一个偶环的边全部替换,将得到两个大小为原先一半的环。
    如果将某一个环的边不完全替换(不包括不替换的情况),一定得到一个基环树。

    那么我们可以将 a 序列中相同大小的环放在一起计数,把 a 序列中每一个基环树进行计数。
    环的情况一定是全部替换/全部不替换,上面的讨论已经包含了所有情况。

    考虑基环树。首先你自己手玩一下,发现基环树如果要合法,一定是一个环 + 某些环上的点延伸出去恰好一条链。
    其实比较容易理解。替换后每个点的入度最多为 2,且入度为 2 的一定是环上的点。

    理论上延伸出去一条链的环上的点会贡献 2 倍方案数:可能延伸出去的链是被替换的;可能环上的入边是被替换的。但是有不合法的情况。
    不妨记延伸出去链长为 x,顺着入边走找到的最近的有链延伸出去的点距离为 y。
    当 x < y 时,延伸出去的链才可能是被替换的;当 x <= y 时,环上的入边才可能是被替换的。
    这个自己手玩一下就能发现了。

    @accepted code@

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    #define ILLEGAL puts("0"), exit(0)
    
    const int MAXN = 100000;
    const int MOD = int(1E9) + 7;
    const int INV2 = (MOD + 1) / 2;
    
    int pow_mod(int b, int p) {
    	int ret = 1;
    	for(int i=p;i;i>>=1,b=1LL*b*b%MOD)
    		if( i & 1 ) ret = 1LL*ret*b%MOD;
    	return ret;
    }
    
    int fct[MAXN + 5], ifct[MAXN + 5], f[MAXN + 5];
    int comb(int n, int m) {
    	return 1LL*fct[n]*ifct[m]%MOD*ifct[n-m]%MOD;
    }
    void init() {
    	fct[0] = 1;
    	for(int i=1;i<=MAXN;i++)
    		fct[i] = 1LL*fct[i-1]*i%MOD;
    	ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
    	for(int i=MAXN-1;i>=0;i--)
    		ifct[i] = 1LL*ifct[i+1]*(i+1)%MOD;
    	f[0] = 1;
    	for(int i=2;i<=MAXN;i+=2)
    		f[i] = 1LL*comb(i, 2)*f[i-2]%MOD*pow_mod(i/2, MOD-2)%MOD;
    }
    
    int a[MAXN + 5], N;
    bool tag[MAXN + 5], vis[MAXN + 5];
    int cnt[MAXN + 5], b[MAXN + 5], ind[MAXN + 5];
    
    int main() {
    	init(), scanf("%d", &N);
    	for(int i=1;i<=N;i++)
    		scanf("%d", &a[i]), ind[a[i]]++;
    	for(int i=1;i<=N;i++)
    		if( ind[i] >= 3 ) ILLEGAL;
    	for(int i=1;i<=N;i++) {
    		if( ind[i] == 2 ) {
    			if( tag[i] ) continue;
    			int p = i;
    			while( !vis[p] )
    				vis[p] = true, p = a[p];
    			if( p != i ) ILLEGAL;
    			p = i;
    			do {
    				tag[p] = true, p = a[p];
    			}while( p != i );
    		}
    	}
    	for(int i=1;i<=N;i++) {
    		if( ind[i] == 0 ) {
    			int t = 0, p = i;
    			while( !tag[p] )
    				vis[p] = true, t++, p = a[p];
    			b[p] = t;
    		}
    	}
    	int ans = 1;
    	for(int i=1;i<=N;i++) {
    		if( b[i] ) {
    			int t = 0, p = i;
    			do {
    				t++, p = a[p];
    			}while( !b[p] );
    			if( t < b[p] ) ILLEGAL;
    			else if( t > b[p] ) ans = 2LL*ans%MOD;
    		}
    	}
    	for(int i=1;i<=N;i++) {
    		if( !vis[i] ) {
    			int t = 0, p = i;
    			do {
    				vis[p] = true, t++, p = a[p];
    			}while( !vis[p] );
    			cnt[t]++;
    		}
    	}
    	for(int i=1;i<=N;i++) {
    		int del = 0;
    		for(int j=0;j<=cnt[i];j+=2) {
    			int tmp = 1;
    			tmp = 1LL*tmp*comb(cnt[i], j)%MOD*f[j]%MOD*pow_mod(i, j/2)%MOD;
    			if( (i & 1) && i != 1 ) {
    				tmp = 1LL*tmp*pow_mod(2, cnt[i] - j)%MOD;
    			}
    			del = (del + tmp) % MOD;
    		}
    		ans = 1LL*ans*del%MOD;
    	}
    	printf("%d
    ", ans);
    }
    

    @details@

    其实细节比较多。不过这道题最奇妙的是数形结合利用建图简化思维。

  • 相关阅读:
    Android程序对不同手机屏幕分辨率自适应的方法
    用户管理和身份验证
    vue----scoped独立样式作用域
    vue----component动态组件
    css----行内元素&&块状元素
    html----rem结合vw布局
    js----jsonp原理
    js----白屏事件&&dom ready时间
    js----var a=b=2解析
    js----常见的表示false的有哪些
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12416577.html
Copyright © 2011-2022 走看看