• CH3602 Counting Swaps(组合计数)


    前置:多重集排列组合问题
    设:多重集(S = left{ n_1 cdot a_1 , n_2 cdot a_2 ,..., n_k cdot a_k ight}.) 即由(n_1)(a_1)(n_2)(a_2)......组成的集合,(n = n_1 + n_2 +...+n_k)
    其全排列的个数为:

    [egin{aligned} frac{n!}{n_1!cdot n_2! cdot ... cdot n_k!} end{aligned} ]

    设整数(r le n_i (forall i in [1,k])),从 S 中取 r 个元素的方案数为:

    [egin{aligned} C^{k - 1}_{r + k - 1} end{aligned} ]

    证明:
    设取了 (x_i)(a_i)(i in [1, k]) ,因为 (r le n_i),所以 (x_i le n_i) ,在 r 个 1的字符串中插入k - 1个0,将其分成 k 段,第 i 段的 1 数量表示 (x_i) ,所以,此问题转化为 k - 1 个 0 和 r 个 1 的全排列问题,所以:(frac{(r + k - 1)!}{r! cdot (k - 1)!} = C^{k - 1}_{r + k - 1})

    题目:3602 Counting Swaps
    题意:给定一个 1~n 的排列 (p_1,p_2,…,p_n),进行若干次操作,每次选择两个整数 x,y,交换 (p_x,p_y)。设把 (p_1,p_2,…,p_n) 变成排列 (1,2,…,n) 至少需要 m 次交换。求有多少种操作方法可以只用 m 次交换。输出对 10^9+9 取模之后的值。1≤n≤10^5。

    将n个数看成n个点,将第 i 个点向第 (p_i) 个点连一条边,当排列到达最终状态时,整个图为 n 个自环,所以我们只需要找出图中的所有环,然后将其拆为自环。
    若 x ,y 位于两个不同的环内,交换这两个数显然会使总环数 - 1,所以这种操作没有意义,我们只需要在同一个环内操作。
    性质: 将一个 n 个点的环拆为 n 个自环最少需要 n - 1 步操作。 可用数学归纳法证明。
    设: 整张图有 m 个环,每个环的结点数分别为 (s_1, s_2 , ... , s_m)
    设: (f_n) 为将一个 n 个点的环拆为自环的方案数,(T_{(x, y)}) 为将一个 n 个点的环拆为两个点数为 x,y 的环这一次操作的方案数,x + y = n,可得:

    [ T_{(x, y)} = left{ egin{aligned} x + y qquad qquad x e y \ x quad qquad qquad x = y end{aligned} ight. ]

    一个环拆成两个环之后,接下来对这两个环的操作就互相独立了,并且 x 环 x - 1 次,y 环 y - 1 次,这 x + y - 2 次操作也可以看成一个多重集全排列,所以:

    [egin{aligned} f_n = sum_{x + y = n}^{x leq frac{n}{2} } f_x cdot f_y cdot T_{(x, y)} cdot frac{(x + y - 2)! }{(x - 1)! cdot (y - 1)! } end{aligned} ]

    再看最初的 m 个环,第(s_i) 个环操作 (s_i - 1) 次,每次操作可以从 m 个环中任选一个,这样又构成一个多重集全排列:

    [Ans = (prod_{i = 1}^m {f_{s_i}} ) cdot frac{(n - m)! }{ (s_1 - 1)! cdot (s_2 - 1)! cdot ... cdot (s_m - 1)! } ]

    这样时间复杂度为(O(n^2logn)),超时。
    打表输出 (f_n) 的前几项:

    1 1 3 16 125 1296 16807 262144 4782969 100000000 2357947691
    

    可以发现规律(f_n = n^{ n - 2}), 这样可以将复杂度降到(O(nlogn))

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    typedef long long lld;
    const lld p = 1e9 + 9;
    const int N = 100005;
    int nextt[N], to[N], head[N], cnt = 0;
    int n, tot = 0;
    lld a[N], s[N], f[N];
    bool vis[N];
    void add(int x, int y) {
    	head[x] = nextt[++cnt];
    	to[cnt] = y; head[x] = cnt;
    }
    lld powe(lld a, lld b) {
    	if(b < 0) return 1;
    	lld base = 1; 
    	while(b) {
    		if(b & 1) base = base * a % p;
    		a = a * a % p; b >>= 1;
    	}
    	return base;
    }
    void dfs(int x) {
    	s[tot]++; vis[x] = true;
    	for(int i = head[x]; i; i = nextt[i]) {
    		int y = to[i]; if(vis[y]) continue;
    		dfs(y);
    	}
    }
    int main() {
    //	freopen("data.in", "r", stdin);
    	int T; scanf("%d", &T); 
    	f[0] = 1;
    	for(lld i = 1; i < N; i++) {
    		f[i] = f[i - 1] * i % p;
    	}
    	while(T--) {
    		memset(s, 0, sizeof(s));
    		memset(vis, false, sizeof(vis));
    		memset(nextt, 0, sizeof(nextt));
    		memset(head, 0, sizeof(head));
    		tot = 0; cnt = 0;
    		scanf("%d", &n);
    		for(int i = 1; i <= n; i++) {
    			scanf("%lld", &a[i]); add(i, a[i]);
    		}
    		for(int i = 1; i <= n; i++) {
    			if(vis[i]) continue;
    			vis[i] = true; ++tot; dfs(i);
    		}
    		lld ans = 1;
    		ans = ans * f[n - tot] % p;
    		for(int i = 1; i <= tot; i++) {
    			ans = ans * powe(s[i], s[i] - 2) % p;
    			ans = ans * powe(f[s[i] - 1], p - 2) % p;
    		}
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    IOS8修改状态栏颜色
    iOS文件存储路径规定
    iOS+HTML5
    调用电话/获取通讯录
    iOS高级必备
    CoreData
    IOS 中的CoreImage框架
    CoreText
    CoreGpaphics
    iOS多线程 NSThread/GCD/NSOperationQueue
  • 原文地址:https://www.cnblogs.com/mcggvc/p/12830348.html
Copyright © 2020-2023  润新知