zoukankan      html  css  js  c++  java
  • HAOI2018 染色

    设 G[k] 表示恰好出现 S 次的颜色恰好有 k 种, 数值为方案总数。设 n = min(M, floor(N/S)), 这道题的答案就是 Σ1≤i≤n Wi × G[i]。

    设 F[k] 表示恰好出现 S 次的颜色至少有 k 种, 数值为方案总数。这个很好算, 先选出颜色 (dbinom Mk), 然后把没选出的颜色当成无色, 按照可重排列的方式构造出所有局面 (dfrac{N!}{(S!)^k(N-S*k)!}), 最后把所有无色填上颜色 ((M-k)^{N-S*k})

    很显然的,

    [F[k] = sum_{i = k}^n inom ik * G[i] ]

    为什么会有二项式系数呢?因为同一个方案会以这些数量的 “F特有的” 方式被观测到。

    现在二项式反演, 得到:

    [G[k] = sum_{i = k}^n (-1)^{n - k}inom ik F[i] ]

    现在得到了一个 O(n2) 的算法。


    拆解一下:

    [G[k] = sum_{i=k}^n (-1)^{i-k}frac{i!}{k!(i-k)!}F[i] \ G[k] * k! = sum_{i=k}^n left(frac{(-1)^{i-k}}{(i-k)!} ight)*(F[i]*i!) ]

    (A[i] = dfrac{(-1)^i}{i!})(B[i] = F[i] * i!), 那么

    [G[k] * k! = sum_{i = k}^n A[i - k] * B[i] ]

    把 B 翻转一下变成 (hat B[n - i] = B[i]), 就有:

    [G[k] * k! = sum_{i = k}^n A[i-k] * hat B[n - i] \ G[k] * k! = sum_{i = 0}^{n-k} A[i] * hat B[n - k - i] ]

    这是 ((A * hat B)[n - k])

    本题做法就清晰明了了。


    对于模数, 首先, 测试的结果是模数是质数, 模数减一的质因数分解是 221 × 479。

    原根暂时不会, 就拿题解的原根来用把(

    #include<bits/stdc++.h>
    typedef long long LL;
    using namespace std;
    
    #define int long long
    
    const int maxn = 4e5 + 23, mo = 1004535809;
    int ksm(int a, int b) {
    	int res = 1;
    	for(; b; b = b>>1, a = ((LL)a * a) % mo)
    		if(b & 1) res = ((LL)res * a) % mo;
    	return res;
    }
    const int g = 3, ig = ksm(g, mo - 2);
    
    int N, M, S;
    int fac[10000003], ifac[10000003];
    int C(int n, int m) {
    	return m > n ? 0 : (LL)fac[n] * (LL)ifac[m] % mo * (LL)ifac[n - m] % mo;
    }
    int calcF(int i) {
    	return (LL)C(M, i) * fac[N] % mo * ksm(ifac[S], i) % mo
    						* ifac[N - S * i] % mo * ksm(M - i, N - S * i) % mo;
    }
    
    int rv[maxn];
    void NTT(int *a, int n, int type) {
    	for(int i = 0; i < n; ++i) if(i < rv[i]) swap(a[i], a[rv[i]]);
    	for(int m = 2; m <= n; m = m << 1) {
    		int w = ksm(type == 1 ? g : ig, (mo - 1) / m);
    		for(int i = 0; i < n; i += m) {
    			int tmp = 1;
    			for(int j = 0; j < (m >> 1); ++j) {
    				int p = a[i + j], q = (LL)tmp * a[i + j + (m >> 1)];
    				a[i + j] = (p + q) % mo, a[i + j + (m >> 1)] = (p - q + mo) % mo;
    				tmp = (LL)tmp * w % mo;
    			}
    		}
    	}
    	if(type == -1) {
    		int Inv = ksm(n, mo - 2);
    		for(int i = 0; i < n; ++i) a[i] = (LL)a[i] * Inv % mo;
    	}
    }
    
    int n, W[maxn];
    int A[maxn], B[maxn], G[maxn];
    
    signed main()
    {
    	scanf("%lld%lld%lld", &N, &M, &S);
    	for(int i = 0; i <= M; ++i) scanf("%lld", &W[i]);
    	n = min(M, N / S);
    	int D = max(N, max(M, S));
    	fac[0] = 1;
    	for(int i = 1; i <= D; ++i) fac[i] = (LL)i * (LL)fac[i - 1] % mo;
    	ifac[D] = ksm(fac[D], mo - 2);
    	for(int i = D; i > 0; --i) ifac[i - 1] = (LL)ifac[i] * (LL)i % mo;
    	
    	for(int i = 0; i <= n; ++i) {
    		A[i] = ifac[i]; if(i & 1) A[i] = (mo - A[i]);
    		B[i] = (LL)fac[i] * (LL)calcF(i) % mo;
    	}
    	reverse(B, B + n + 1);
    	
    	int len = 1; while(len < ((n+1) << 1)) len = len << 1;
    	for(int i = 1; i < len; ++i) rv[i] = (rv[i>>1]>>1) | ((i&1) ? len>>1 : 0);
    	NTT(A, len, 1), NTT(B, len, 1);
    	for(int i = 0; i < len; ++i) G[i] = (LL)A[i] * (LL)B[i] % mo;
    	NTT(G, len, -1);
    	reverse(G, G + n + 1);
    	for(int i = 0; i <= n; ++i) G[i] = (LL)G[i] * ifac[i] % mo;
    	long long ans = 0ll;
    	for(int i = 0; i <= n; ++i) ans = ans + ((LL)G[i] * (LL)W[i] % mo), ans = ans % mo;
    	cout << (ans % mo + mo) % mo;
    	return 0;
    }
    
  • 相关阅读:
    【JavaWeb】Hibernate Dao层设计
    【C#】身份证识别(一):身份证号定位
    【JavaWeb】SpringMVC重定向页面取值
    关于chrome扩展的东西
    【C#】基于Opencv/Emgucv的身份证识别
    Bashware
    【java多线程】分段阶乘计算
    【java】多项式计算(中缀转后缀)
    iOS  OTA问题排查【itms-servers协议在线安装ipa包】
    【深度学习】BP算法分类iris数据集
  • 原文地址:https://www.cnblogs.com/tztqwq/p/14329160.html
Copyright © 2011-2022 走看看