AtCoder Beginner Contest 352 G(期望)

题意:有n种袜子,给定每种袜子数量ai,每次抽取袜子不放回,求拿到两支同种袜子的期望次数n≤3e5,ai≤3000n\le3e5,a_i\le3000n3e5,ai3000
Solution: 期望、生成函数、分治
\quad期望E=∑i∗qiE=\sum i * q_iE=iqi,其中qiq_iqi表示恰好第i次抽取到两只同种袜子的概率,即前i-1次抽取的袜子各不相同,第i次抽到之前出现过的袜子的概率
\quadpip_ipi,表示前i次抽取的袜子各不相同的概率,考虑p和q关系,可以发现qi=pi−1−piq_i=p_{i-1}-p_iqi=pi1pi,因为pi−1p_{i-1}pi1表示前i-1次抽取结果各不相同,而第i次的抽取可能会出现之前同种袜子或者不出现,pip_{i}pi表示前i次抽取结果各不相同,即前i-1次抽取结果各不相同,第i次抽取出现了之前种类的袜子,可以理解为pip_ipi是包含在pi−1p_{i-1}pi1中的子事件,相减后即是前i-1次抽取结果各不相同,第i次抽到之前出现过的袜子类型,因此有如上关系式。
\quad于是我们化简式子E=∑i∗qi=∑i(pi−1−pi)=∑i=2n+1(ipi−1−ipi)=2(p1−p2)+3(p2−p3)....+(n+1)(pn−pn+1)=2p1+p2+p3+...+pn+pn+1,其中p1=1,pn+1=0(鸽巢原理)E=\sum i*q_i=\sum i(p_{i-1} - p_i)=\sum_{i=2}^{n+1} (ip_{i-1}-ip_i)=2(p_1-p_2)+3(p_2-p_3)....+(n+1)(p_n-p_{n+1})=2p_1+p_2+p_3+...+p_n+p_{n+1},其中p_1=1, p_{n+1}=0(鸽巢原理)E=iqi=i(pi1pi)=i=2n+1(ipi1ipi)=2(p1p2)+3(p2p3)....+(n+1)(pnpn+1)=2p1+p2+p3+...+pn+pn+1,其中p1=1,pn+1=0(鸽巢原理)
\quad最终转化成求pip_ipi,设tot=∑aitot=\sum a_itot=ai,分母为总方案数即CtotiC_{tot}^{i}Ctoti,分子为[xi]∏j=1n(1+ajx)[x^i]\prod_{j=1}^{n}(1+a_jx)[xi]j=1n(1+ajx),这里用生成函数意义求解,通俗理解就是每种袜子里取1或者0个即Cak1C_{a_k}^{1}Cak1orCak0C_{a_k}^{0}Cak0,生成函数意义可以解决这个问题。求此式子时候使用分治。复杂度O(nlogn^2)
\quad本题的关键点我认为是考虑到p和q的关系,正难则反

const int N = 2e5+5;
const int mod = 998244353;
const int maxn = 6e6+10;
const int p = 998244353;
int Pow(int x,int d){
    int tans = 1;
    if(d == 0)return 1%p;
    int a = Pow(x,d/2);
    tans = 1ll*a*a%p;
    if(d%2)tans = 1ll*tans*x%p;
    return tans%p;
}
typedef vector<int> Poly;//多项式定义
int F1[maxn],F2[maxn];
int rev[maxn];
void NTT(int * A,int lim,int opt) {
    int i, j, k, m, gn, g, tmp;
    for(int i = 0; i < lim; ++i)rev[i] = (i & 1)*(lim >> 1) + (rev[i >> 1] >> 1);
    for(i = 0; i < lim; ++i)if (rev[i] < i) swap(A[i], A[rev[i]]);
    for(m = 2; m <= lim; m <<= 1) {
        k = m >> 1;
        gn = Pow(3,(p - 1) / m);
        for(i = 0; i < lim; i += m) {
            g = 1;
            for (j = 0; j < k; ++j, g = 1ll * g * gn % p) {
                tmp = 1ll * A[i + j + k] * g % p;
                A[i + j + k] = (A[i + j] - tmp + p) % p;
                A[i + j] = (A[i + j] + tmp) % p;
            }
        }
    }
    if(opt == -1){
        reverse(A+1,A+lim);
        int inv = Pow(lim,p-2);
        for(i = 0; i < lim; ++i) A[i] = 1ll * A[i] * inv % p;
    }
}
Poly mul(const Poly & A,const Poly & B){
    int n = A.size(),m = B.size();
    int siz = n + m - 1;
    Poly C(siz);
    if(siz < 64){//小于等于64项直接暴力算
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++)C[i+j] = (C[i+j] + 1LL*A[i]*B[j]%p)%p;
        }
        return C;
    }
    int fsiz = 1;
    while(fsiz <= siz)fsiz <<=1;
    for(int i = 0;i < fsiz;i++)F1[i] = F2[i] = 0;
    for(int i = 0;i < n;i++)F1[i] = A[i];
    for(int i = 0;i < m;i++)F2[i] = B[i];
    NTT(F1,fsiz,1);
    NTT(F2,fsiz,1);
    for(int i = 0;i < fsiz;i++)F1[i] = 1ll*F1[i]*F2[i]%p;
    NTT(F1,fsiz,-1);
    for(int i = 0;i < siz;i++){
        C[i] = F1[i];
    }
    return C;
}
vector<int> a;
Poly cal(int l ,int r){
    if(l == r){
        Poly ans = {1, a[l]};
        return ans;
    }
    int mid = (l+r)>>1;
    return mul(cal(l,mid),cal(mid+1,r));
}
int n;
signed main(){
    cin >> n;
    a.resize(n, 0);
    for(int &x : a){
        cin >> x;
    }
    int tot = accumulate(a.begin(), a.end(), 0);
    auto f = cal(0, n - 1);
    int ans = 0, tmp = 1;
    for(int i = 0; i <= n; i++){
        ans = (ans + f[i] * Pow(tmp, mod - 2) % mod) % mod;
        tmp =  tmp * (tot - i) % mod;
        tmp = tmp * Pow(i + 1, mod - 2) % mod;
    }
    cout << ans;
}   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值