CF1194F Crossword Expert(数论,组合数学)

不难的一题。不知道为什么能 $2500$……

不过场上推错了一直不会优化……

首先考虑 $f_i$ 表示恰好做完前 $i$ 道题的概率。

这样很难算。修改一下,$f_i$ 表示做完至少 $i$ 道题的概率。

答案就是 $\sum\limits_{i=0}^ni(f_i-f_{i+1})=\sum\limits_{i=1}^nf_i$。

由于每道题只可能多用至多一秒,考虑 $dp[i][j]$ 为前 $i$ 道题恰好SB $j$ 次的概率。

初始状态是 $dp[0][0]=1$。转移是 $dp[i][j]=\dfrac{1}{2}(f[i-1][j]+f[i-1][j-1])$。

盯着式子看不难看出 $dp[i][j]=(\dfrac{1}{2})^i\dbinom{i}{j}$。用实际意义也不难理解。

(场上就是这里推成了 $(\dfrac{1}{2})^{i+j}\dbinom{i+j}{i}$ 就自闭了……)

那么有 $f_i=\sum\limits_{j=0}^{r_i}dp[i][j]=(\dfrac{1}{2})^i\sum\limits_{j=0}^{r_i}\dbinom{i}{j}$。其中 $r_i=T-\sum\limits_{j=1}^it_j$,表示最多允许SB几次。(其实要和 $i$ 取个 $\min$,但是不影响,可以想一想为什么)

问题就是求 $\sum\limits_{j=0}^{r_i}\dbinom{i}{j}$ 了。接下来是一个很妙的做法。

首先 $i=1$ 时直接暴力。

然后 $\sum\limits_{j=0}^{r_i}\dbinom{i+1}{j}=\sum\limits_{j=0}^{r_i}(\dbinom{i}{j}+\dbinom{i}{j-1})=2\sum\limits_{j=0}^{r_i}\dbinom{i}{j}-\dbinom{i}{r_i}$。可以直接递推。

由于 $r_i$ 单调递减,递推完之后把 $r_{i+1}+1$ 到 $r_i$ 的组合数都删掉就行了。

实现优秀一点可以做到 $O(n)$。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=200020,mod=1000000007,inv2=500000004;
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
    ll x=0,f=0;char ch=getchar();
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,t[maxn],fac[maxn],inv[maxn],invfac[maxn],f[maxn],ans,pro=1;
ll r[maxn];
int C(int n,ll m){
    if(n<m) return 0;
    return 1ll*fac[n]*invfac[m]%mod*invfac[n-m]%mod;
}
int main(){
    n=read();r[0]=read();
    FOR(i,1,n) t[i]=read();
    FOR(i,1,n) r[i]=r[i-1]-t[i];
    fac[0]=fac[1]=inv[1]=invfac[0]=invfac[1]=1;
    FOR(i,2,n){
        fac[i]=1ll*fac[i-1]*i%mod;
        inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod;
        invfac[i]=1ll*invfac[i-1]*inv[i]%mod;
    }
    FOR(i,0,min(1ll,r[1])) f[1]=(f[1]+C(1,i))%mod;
    FOR(i,2,n){
        if(r[i]<0) break;
        f[i]=(2ll*f[i-1]-C(i-1,r[i-1])+mod)%mod;
        ROF(j,min<ll>(i,r[i-1]),r[i]+1) f[i]=(f[i]-C(i,j)+mod)%mod;
    }
    FOR(i,1,n){
        if(r[i]<0) break;
        pro=1ll*pro*inv2%mod;
        ans=(ans+1ll*pro*f[i])%mod;
    }
    printf("%d\n",ans);
}
View Code

 

posted @ 2019-07-17 21:09  ATS_nantf  阅读(302)  评论(0编辑  收藏  举报