• @loj



    @description@

    (n) 个在范围 ([1, D]) 内的整数均匀随机变量。

    求至少能选出 (m) 个瓶子,使得存在一种方案,选择一些变量,并把选出来的每一个变量放到一个瓶子中,满足每个瓶子都恰好装两个值相同的变量的概率。

    请输出概率乘上 (D^n) 后对 (998244353) 取模的值。

    原题传送门。

    @solution@

    (l = min{n - 2m, D})。不难想到枚举出现次数为奇数的变量个数 (i),列式子:

    [n! imes[x^n]sum_{i=0}^{l}{Dchoose i}(frac{e^x - e^{-x}}{2})^i(frac{e^x + e^{-x}}{2})^{D-i} ]

    然后二项式展开 + 整理化简得到:

    [frac{D!}{2^D} imessum_{i=0}^{D}sum_{p=0}^{i}sum_{q=0}^{D-i} frac{(-1)^{i-p}(2p+2q-D)^n}{p!q!(i-p)!(D-i-q)!} ]

    然后我就到此为止了。不过这个思路是可以继续往下推导的,可以参考这篇博客。出题人也有一个关于这种推导方法的解释(还没仔细看,改天补坑.jpg)。

    有一个较简单的推导方法。上式中出现了 3 个 (sum),远远超过了一个中老年菜鸡选手的承受范围。
    我们不妨设法去掉 1 个,然后再用常规卷积方法优化。

    不妨钦定选 i 个奇数变量,其他的任意选,得到表达式:

    [egin{aligned} g_i &= n! imes{D choose i} imes [x^n](frac{e^x - e^{-x}}{2})^ie^{(D-i)x} \ &= n! imes{D choose i} imes [x^n]sum_{j=0}^i{ichoose j}e^{(2j-2i+D)x} end{aligned} ]

    可以发现我们少了一次二项式展开。把 ({ichoose j}) 拆开就可以直接卷积了。

    (f_i) 表示恰好 i 个奇数变量的方案数,那么有 (g_i = sum_{j=i}^{D}{jchoose i}f_j),二项式反演一下:

    [f_n = sum_{i=n}^{D}(-1)^{i-n}{ichoose n}g_i ]

    这玩意也可以卷积。然后就做完了。

    @accepted code@

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    
    const int MOD = 998244353;
    const int MAXN = 400000;
    
    inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
    inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
    inline int mul(int x, int y) {return 1LL * x * y % MOD;}
    
    int pow_mod(int b, int p) {
    	int ret = 1;
    	for(int i=p;i;i>>=1,b=mul(b,b))
    		if( i & 1 ) ret = mul(ret, b);
    	return ret;
    }
    
    int w[20], iw[20], fct[MAXN + 5], ifct[MAXN + 5], inv[MAXN + 5];
    void init() {
    	for(int i=0;i<20;i++) {
    		w[i] = pow_mod(3, (MOD - 1) / (1 << i));
    		iw[i] = pow_mod(w[i], MOD - 2);
    	}
    	fct[0] = 1; for(int i=1;i<=MAXN;i++) fct[i] = mul(fct[i - 1], i);
    	ifct[MAXN] = pow_mod(fct[MAXN], MOD - 2);
    	for(int i=MAXN-1;i>=0;i--) ifct[i] = mul(ifct[i + 1], i + 1);
    	for(int i=1;i<=MAXN;i++) inv[i] = mul(fct[i - 1], ifct[i]);
    }
    void ntt(int *A, int n, int type) {
    	for(int i=0,j=0;i<n;i++) {
    		if( i < j ) swap(A[i], A[j]);
    		for(int l=(n>>1);(j^=l)<l;l>>=1);
    	}
    	for(int i=1,s=2,t=1;s<=n;i++,s<<=1,t<<=1) {
    		int u = (type == 1 ? w[i] : iw[i]);
    		for(int j=0;j<n;j+=s) {
    			for(int k=0,p=1;k<t;k++,p=mul(p,u)) {
    				int x = A[j + k], y = mul(p, A[j + k + t]);
    				A[j + k] = add(x, y), A[j + k + t] = sub(x, y);
    			}
    		}
    	}
    	if( type == -1 ) {
    		for(int i=0;i<n;i++)
    			A[i] = mul(A[i], inv[n]);
    	}
    }
    int length(int n) {
    	int len; for(len = 1; len <= n; len <<= 1);
    	return len;
    }
    int comb(int n, int m) {
    	return mul(fct[n], mul(ifct[m], ifct[n - m]));
    }
    int D, n, m;
    int f[MAXN + 5], g[MAXN + 5], a[MAXN + 5], b[MAXN + 5];
    void get() {
    	for(int i=0;i<=D;i++) a[i] = mul((i & 1 ? MOD - 1 : 1), mul(pow_mod(sub(D, 2*i), n), ifct[i]));
    	for(int i=0;i<=D;i++) b[i] = ifct[i];
    	int len = length(2*D); ntt(a, len, 1), ntt(b, len, 1);
    	for(int i=0;i<len;i++) g[i] = mul(a[i], b[i]);
    	ntt(g, len, -1);
    	for(int i=1;i<=D;i++) g[i] = mul(g[i], mul(comb(D, i), mul(fct[i], pow_mod(2, MOD - 1 - i))));
    	for(int i=D+1;i<len;i++) g[i] = 0;
    	
    	for(int i=0;i<len;i++) a[i] = b[i] = 0;
    	for(int i=0;i<=D;i++) a[D - i] = mul(g[i], fct[i]);
    	for(int i=0;i<=D;i++) b[i] = (i & 1 ? sub(0, ifct[i]) : ifct[i]);
    	ntt(a, len, 1), ntt(b, len, 1);
    	for(int i=0;i<len;i++) f[i] = mul(a[i], b[i]);
    	ntt(f, len, -1), reverse(f, f + D + 1);
    	for(int i=0;i<=D;i++) f[i] = mul(f[i], ifct[i]);
    }
    
    int main() {
    	init(), scanf("%d%d%d", &D, &n, &m), get();
    	int l = min(D, n - 2*m), ans = 0;
    	for(int i=0;i<=l;i++) ans = add(ans, f[i]);
    	printf("%d
    ", ans);
    }
    

    @details@

    感觉第二种做法比第一种做法更自然一些?(虽然我觉得一般都会先想到第一种做法)

    看了一下某谷的题解,发现好像还有线性做法?

    我感觉我的组合数学现在可能只会卷积模板题了。

  • 相关阅读:
    【NOIP2001】【Luogu1025】数的划分(可行性剪枝,上下界剪枝)
    【POJ2676】Sudoku(优化搜索顺序)
    【codevs4228】小猫爬山(最优化剪枝)
    实现两个路由器漫游(传统路由器做AP)
    查询数据SELECT 之单表查询
    MySQL数据库基础概念
    删除Mac上的mysql数据库
    MYSQL数据库
    并发编程
    socketserver及相关的类 (处理socket服务端)+ event事件的使用
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/13045259.html
Copyright © 2020-2023  润新知