• [UNR #3]百鸽笼


    我好菜啊,就在网上看了一堆博(dai)客(ma),终于搞懂了一点

    思路

    因为每列满不满不好处理,用容斥。

    (L)有空位等价于其他列都比(L)先满(限制)。

    现在考虑一个列(L)的概率,有一个列集合(S)(S)中都要比(i)先空,(S)就相当于必须违反限制的集合。

    (S)带上一个容斥系数((-1)^{|S|}),加起来就是答案。

    可以DP计数(S)的方案数,发现容斥系数只和(|S|)有关,所以就不枚举(S)了,改成记录(|S|)和共进入了多少人(len)即可。

    因为这个背包DP是计数,所以很容易撤销,后面具体讲。

    DP的方法

    这块是我花时间最多的地方,基本上其他地方都没讲。(可能是我太菜了吧)

    (f_{i,j})表示(|S|=i),总共有(j)个人((len=j))的方案数。

    最重要的就是要保证除了计算的那列可以放满,其他的都放不满(这样保证放的列数不变),所以(cnt)(-1),留出一个不能放!不保证这个,刚才的容斥就没有意义了。

    [f_{i +1, j + c} = f_{i, j} cdot inom{j+c}{c} ]

    意思是这一列在(L)之前填了(c)(c=0)是允许的),然后把这(c)个插入操作序列的方案数。

    最后再把(L)列的元素插入到排列中,注意只插(a_L-1)个,最后一个保证插到最后,因为(L)列满了后面的都不考虑,这样避免重复计数

    最后,因为之前已经保证了不会在(L)列前把其他列选完(减了(1)个),所以每次都可以任选一列放入,因此总方案数就是((|S|+ 1) ^ {len}),就是分母了。
    // 这个解释不是很合理,先咕了

    怎么撤销

    因为加一个物品是从后往前,每次用旧的值去更新成新的值。

    所以撤销时要从前往后,从(i=0, j=0)开始(始终是原值),把新值再变回旧值,再往后更新。

    Code

    没有对任何边界情况做处理,所以常数非常大。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    #define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
    typedef long long ll;
    
    const int N = 35, Sum = 935, p = 998244353;
    inline int add(int x, int y){return x+y>=p ? x+y-p : x+y;}
    inline int sub(int x, int y){return x-y<0 ? x-y+p : x-y;}
    inline int mul(int x, int y){return 1LL * x * y % p;};
    inline int power(int x, int y){
    	int res = 1;
    	for(; y; y>>=1, x = mul(x, x)) if(y & 1) res = mul(res, x);
    	return res;
    }
    int a[N];
    int n, sum = 0;
    int powinv[N][Sum], C[Sum][Sum];
    int f[N][Sum];
    
    void pre(int n, int sum){
    	for(int i=1; i<=n; i++){
    		powinv[i][0] = 1; powinv[i][1] = power(i, p-2);
    		// printf("powinv[%d][%d] = %d
    ", i, 1, powinv[i][1]);
    		for(int j=2; j<=sum; j++)
    			powinv[i][j] = mul(powinv[i][j-1], powinv[i][1]);
    	}
    	C[0][0] = 1;
    	for(int i=1; i<=sum; i++){
    		C[i][0] = 1;
    		for(int j=1; j<=i; j++) C[i][j] = add(C[i-1][j], C[i-1][j-1]);
    	}
    }
    
    void pack(int cnt){
    	for(int i=n-1; i>=0; i--)
    		for(int j=sum; j>=0; j--)
    			for(int c=0; c<cnt; c++) // 不能全进
    				f[i+1][j+c] = sub(f[i+1][j+c], mul(f[i][j], C[j+c][c]));
    }
    void unpack(int cnt){
    	for(int i=0; i<n; i++)
    		for(int j=0; j<=sum; j++)
    			for(int c=0; c<cnt; c++)
    				f[i+1][j+c] = add(f[i+1][j+c], mul(f[i][j], C[j+c][c]));
    }
    
    int main(){
    	scanf("%d", &n);
    	for(int i=1; i<=n; i++) scanf("%d", a + i), sum += a[i];
    	pre(n, sum);
    	f[0][0] = 1;
    	for(int i=1; i<=n; i++) pack(a[i]);
    	for(int i=1; i<=n; i++){
    		unpack(a[i]);
    		int res = 0;
    		for(int j=0; j<n; j++)
    			for(int k=0; k<=sum - a[i]; k++)
    				res = add(res, mul(powinv[j+1][k + a[i]], mul(f[j][k], C[k + a[i] - 1][a[i] - 1])));//, printf("res = %d
    ", res);
    		printf("%d ", res);
    		pack(a[i]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    0是字符串的终止符
    c语言中获取数组的长度写法
    c语言第一个程序
    linux下adb连接不上解决方法
    android的apk权限查看
    dumpsys netpolicy中state的含义
    查看ps和dumpsys netpolicy
    批量安装/卸载手机apk--python语言
    【转载】Think as Customer 以客户为中心的测试理念
    利用xampp进行https操作
  • 原文地址:https://www.cnblogs.com/RiverHamster/p/sol-uoj390.html
Copyright © 2020-2023  润新知