Codeforces - 1194F - Crossword Expert - 组合数学

https://codeforc.es/contest/1194/problem/F

下面是错的。

看起来有点概率dp的感觉?
给你T秒钟时间,你要按顺序处理总共n个事件,每个事件处理花费的时间是ti秒钟,有一半的概率失手导致多花1秒钟。求T时间内处理完事情的总数的期望。

处理完第1个事件,有0.5概率花t1,有0.5概率花t1+1。
处理完第2个事件,有0.25概率花t1+t2,有0.5概率花t1+t2+1,有0.25概率花t1+t2+2。
处理完第3个事件,有0.125概率花t1+t2+t3,有0.375概率花t1+t2+t3+1,有0.375概率花t1+t2+t3+2,有0.125概率花t1+t2+t3+3。


一开始的思路不对,一开始是按“最后一个结束的元素”来分类,这样子导致非常难计算,应该独立算每个元素的贡献概率。


答案\(E=\sum\limits_{i=1}^{n}P(i)\)\(P(i)\)表示第\(i\)个元素贡献的概率。在前面的很多个里面这样的概率是1。

先累计就算每一次都失手还是能做完的前面的若干个。记第一个不一定能够做完的为x。


第一步,统计结束于\(x\)的,失手次数的上限\(d_x\)不能超过\(x\),也要满足\(pre_x+d_x<=T\),即不能超过\(T-pre_x\)。还要保证最后下一个元素\(x+1\)不能被选到吗?其实并不需要这么做,只要在选下一个的时候加上就可以了。

\(d_x=min(x,T-pre_x)\)

失手\([0,d_x]\)次会导致贡献第\(x\),这样的贡献是

\(P(x)=(\frac{1}{2})^{x}\sum\limits_{i=0}^{d_x}C_x^i\)


第二步,统计结束于\(x+1\)的,失手次数\(d_{x+1}\)不能超过\(x+1\),也要满足\(pre_{x+1}+d_{x+1}<=T\),即不能超过\(T-pre_{x+1}\)

\(d_{x+1}=min(x+1,T-pre_{x+1})\)

失手\([0,d_{x+1}]\)次会导致贡献\(x+1\),这样的贡献是

\(P(x+1)=(\frac{1}{2})^{x+1}\sum\limits_{i=0}^{d_{x+1}}C_{x+1}^i\)


最后是一个奇怪的效率问题了。应该也是这道题第二值得学习的地方(第一值得学习的是统计的思路)。

第一个是要注意到组合数的一个变形:

\(C_n^k+C_{n}^{k+1}=C_{n+1}^{k+1}\),也就是按第\(n+1\)个元素有没有被选进这\(k+1\)个元素分类。

所以

\(\sum\limits_{i=0}^{k+1}C_{n+1}^i=C_{n}^{k+1}+C_{n}^{k}+C_{n}^{k}+C_{n}^{k-1}+...+C_{n}^{1}+C_{n}^{0}+C_{n}^{0}\)

也就是:

\(\sum\limits_{i=0}^{k+1}C_{n+1}^i=C_{n}^{k+1}+2*\sum\limits_{i=0}^{k}C_{n}^i\)

第二个是要注意到\(d_{x+1}<=d_{x}+1\),这不是很显然吗,多一件事情肯定要花多至少1秒去处理,就必须少失误一次?这个东西可以保证,上标是单调下降的。

所以可以O(n)转移出所有需要的组合数。因为上下标都是单调的。


还是有了两处溢出错误以及一个T==0也是合法情况的问题。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
const int inv2 = (mod + 1) >> 1;

int n, curn, curk;
ll T, sigmaC, E, inv2x;

const int MAXN = 2e5;

ll inv[MAXN + 5], fac[MAXN + 5], invfac[MAXN + 5];

void init_inv(int n = MAXN) {
    inv[1] = 1;
    for(int i = 2; i <= n; i++) {
        inv[i] = inv[mod % i] * (mod - mod / i) % mod;
    }
}

void init_fac_invfac(int n = MAXN) {
    init_inv(n);
    fac[0] = 1, invfac[0] = 1;
    for(int i = 1; i <= n; i++) {
        fac[i] = fac[i - 1] * i % mod;
        invfac[i] = invfac[i - 1] * inv[i] % mod;
    }
}

inline ll C(ll n, ll m) {
    if(n < m)
        return 0;
    return fac[n] * invfac[n - m] % mod * invfac[m] % mod;
}

int t[200005];
ll pret[200005];

void calc_sigmaC(int x) {
    ll tmp = 0;
    int dx = min((ll)x, T - pret[x]);
    for(int i = 0; i <= dx; i++) {
        tmp += C(x, i);
        if(tmp>=mod)
            tmp-=mod;
    }
    sigmaC = tmp;
    curn = x;
    curk = dx;
}

void next_sigmaC(int nextx) {
    int dx1 = min((ll)nextx, T - pret[nextx]);
    ll tmp = sigmaC;
    tmp = 2ll * tmp % mod;
    tmp = (tmp + C(curn, curk + 1)) % mod;
    curn++;
    curk++;
    while(curk > dx1) {
        tmp = (tmp + mod - C(curn, curk)) % mod;
        curk--;
    }
    sigmaC = tmp;
}

int main() {
#ifdef Yinku
    freopen("Yinku.in", "r", stdin);
    //freopen("Yinku.out", "w", stdout);
#endif // Yinku
    init_fac_invfac();
    while(~scanf("%d%lld", &n, &T)) {
        pret[0] = 0;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &t[i]);
            pret[i] = pret[i - 1] + t[i];
        }
        E = 0;
        inv2x = inv2; //与x配对的sigmaC的系数
        int x = 1;
        while(x <= n && pret[x] + x <= T) {
            x++;
            E++;
            inv2x = (inv2x * inv2) % mod;
        }
        //x现在是第一个不一定可以的
        if(x <= n) {
            calc_sigmaC(x);
            for(; x <= n; x++) {
                E = (E + inv2x * sigmaC % mod) % mod;
                if(T - pret[x + 1] < 0)
                    break;
                next_sigmaC(x + 1);

                inv2x = (inv2x * inv2) % mod;
            }
        }
        printf("%lld\n", E);
    }
}

需要学习的是分类统计的思路,以及组合数直接转移的思想。

转载于:https://www.cnblogs.com/Yinku/p/11189287.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值