zoukankan      html  css  js  c++  java
  • 【题解】 【集训队作业2018】喂鸽子 minmax容斥+期望dp+补集转化 UOJ449

    Legend

    (n) 只鸽子,每秒钟你随机选一只鸽子喂给它 (1) 粒玉米,当一只鸽子吃到 (k) 粒玉米时会饱,求所有鸽子饱的期望时间。

    (0 le n le 50)(0 le k le 1000)。模数 (998244353)

    Editorial

    不难发现可以 ( extrm{min-max}) 容斥。

    (F_i) 为选中 (i) 只鸽子的子集,并给所有 (n) 只鸽子喂玉米,期望喂多少次玉米后,在该子集中第一次出现被喂饱的鸽子。

    根据 ( extrm{min-max}) 容斥,注意到我们不关心子集的具体构成,只关心大小,所以答案即为:

    [sum_{i=1}^{n}inom{n}{i}(-1)^{i+1}F_i ]

    现在问题只有,怎么求 (F_i)。求期望不好求,我们先求方案数,设 (G_{i,j}) 表示喂 (i) 只鸽子玉米,喂了 (j) 次还没有任何鸽子被喂饱的方案数。

    (G) 数组直接背包复杂度高达 (O(n^3k)),过不了,不过可以很容易用 ( extrm{EGF})(指数型生成函数)优化到 (O(n^2klog nk)),但这依赖于本题的特殊模数((998244353))。下介绍一种不用 ( extrm{NTT}) 的方法——补集转化。

    我们考虑用所有方案减去不合法的方案,可以得到如下转移方程:

    [G_{i,j}=i sum_{k=0}^{j-1} G_{i,j-1}-inom{j-1}{k-1}G_{i-1,j-k} ]

    其含义可以这样解释:

    1. 每次我们考虑新增一粒玉米,喂给 (i) 只鸽子中的任意一只。
    2. 这样子会出现不合法情况,即某一只鸽子之前没饱,被喂了之后就饱了。枚举一下这只被喂饱的鸽子是谁,和它是在哪几次被喂过玉米,算出方案减去。

    然后我们就可以通过 (O(n^2k)) 的时间算出 (G)(方案数)。

    那么怎么求期望时间 (F) 呢?

    [F_i = frac{n}{i} imes isum_{j=0}^{(i-1)(k-1)}(j+k) frac{inom{j+k-1}{k-1}G_{i-1,j}}{i^{j+k}} ]

    其含义就是枚举除了饱了的鸽子,其它的鸽子一共吃了多少,然后按期望的定义算就行了。前面乘一个 (frac{n}{i}) 是因为在 (n) 只鸽子内随机时,你只有 (frac{i}{n}) 的概率选到这个子集的鸽子。

    然后就可以毫无压力地做这个题了。

    Code

    #include <bits/stdc++.h>
    
    #define debug(...) fprintf(stderr ,__VA_ARGS__)
    #define __FILE(x)
    	freopen(#x".in" ,"r" ,stdin);
    	freopen(#x".out" ,"w" ,stdout)
    #define LL long long
    
    const int MX = 50 + 2;
    const LL MOD = 998244353;
    const LL phi = MOD - 1;
    
    int read(){
    	char k = getchar(); int x = 0;
    	while(k < '0' || k > '9') k = getchar();
    	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
    	return x;
    }
    
    LL qpow(LL a ,LL b ,LL p = MOD){
    	LL ans = 1;
    	while(b){if(b & 1) ans = ans * a % p;
    		a = a * a % p ,b >>= 1;
    	}return ans;
    }
    
    int n ,k;
    LL dp[MX][50 * 999 + 2] ,S[MX][50 * 999 + 2] ,F[MX];
    // dp[i][j] 表示给 i 只不同的鸽子喂 j 粒玉米且都没饱的方案数
    // F[i] 表示给 i 只鸽子喂饱第一只的期望时间
    
    LL C(int n ,int m);
    
    LL fac[50000 + 23] ,inv[50000 + 23];
    void init(){
    	fac[0] = 1 ,inv[0] = inv[1] = 1;
    	for(int i = 1 ; i <= 50000 ; ++i) fac[i] = fac[i - 1] * i % MOD;
    	for(int i = 2 ; i <= 50000 ; ++i) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
    	for(int i = 2 ; i <= 50000 ; ++i) inv[i] = inv[i] * inv[i - 1] % MOD;
    
    	dp[0][0] = 1;
    	for(int i = 1 ; i <= n ; ++i){
    		dp[i][0] = 1;
    		for(int j = 1 ; j <= (k - 1) * i + 1 ; ++j){
    			dp[i][j] = i * (dp[i][j - 1] - C(j - 1 ,k - 1) % MOD * (j - k >= 0 ? dp[i - 1][j - k] : 0) % MOD + MOD) % MOD;
    		}
    	}
    
    	for(int i = 1 ; i <= n ; ++i){ // 枚举被喂了的总鸽子数量
    		F[i] = 0;
    		for(int j = 0 ; j <= (k - 1) * (i - 1) ; ++j){ // 枚举其它鸽子吃了多少
    			F[i] = (F[i] + C(j + k - 1 ,k - 1) * (j + k) % MOD * dp[i - 1][j]  % MOD * qpow(i ,(j + k) * (MOD - 2) % phi)) % MOD;
    		}
    		// F[i] = F[i] * i % MOD;
    	}
    }
    
    int main(){
    	n = read() ,k = read();
    	init();
    	LL ans = 0;
    	for(int i = 1 ; i <= n ; ++i){
    		ans += (i & 1 ? 1 : -1) * F[i] * C(n ,i) % MOD * n % MOD/* * qpow(i ,MOD - 2) % MOD*/;
    	}
    	ans = (ans % MOD + MOD) % MOD;
    
    	printf("%lld
    " ,ans);
    	return 0;
    }
    
    LL C(int n ,int m){return m > n || m < 0 ? 0 : fac[n] * inv[m] % MOD * inv[n - m] % MOD;}
    
  • 相关阅读:
    Asp.net HttpContext 简介
    cxf 调用 .net webservice
    Dapper.Rainbow 简单使用
    Aspose.words 书签定位
    修改WCF的默认序列化格式
    mysql8.0.18忘记密码后重置密码
    聊一聊c#中的MATH.ROUND方法
    Windows上安装MySql
    win7搭建本地SonarQube环境进行c#代码分析
    使用dapper遇到的问题及解决方法
  • 原文地址:https://www.cnblogs.com/imakf/p/14373681.html
Copyright © 2011-2022 走看看