ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[UNR #3]百鸽笼

2020-01-28 23:52:50  阅读:239  来源: 互联网

标签:UNR return int 鸽笼 Sum len res DP


我好菜啊,就在网上看了一堆博(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 \binom{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\n", 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\n", res);
        printf("%d ", res);
        pack(a[i]);
    }
    return 0;
}

标签:UNR,return,int,鸽笼,Sum,len,res,DP
来源: https://www.cnblogs.com/RiverHamster/p/sol-uoj390.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有