Two Arrays dp+前缀和/组合数学,Letter Picking 博弈 区间dp,P6862 [RC-03] 随机树生成器

70 阅读3分钟

         这次一直进不去确实是搞心态了,但是最后延长时间还是很不错的,做题时的体验还可以,如果自测调试没有冷却时间的话就更好了 

Two Arrays dp+前缀和/组合数学

dp[i][j][k]表示a[i]=j,b[i]=k时符合条件的数组对数,那么dp[i][j][k]=sum(dp[i-1][1~j][k],dp[i-1][1~j][k+1],,,dp[i-1][1~j][k~n]),所以我们考虑前缀和来处理,sum[i][j][k]表示a[i]=1~j,b[i]=k~n时的对数,再转移之前先预处理出来,先选出a[i]=j,b[i]=k~n时的对数,再算出a[i]=1~j,b[i]=k~n的对数;

ll n,m,dp[11][1005][1005],sum[10][1005][1005];
int main(){
    scanf("%lld%lld",&n,&m);
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
    for(int j=1;j<=n;j++)
        for(int k=j;k<=n;k++)
        dp[1][j][k]=1;

    for(int i=2;i<=m;i++){
        for(int j=1;j<=n;j++)
            for(int k=n;k>=j;k--)
                sum[i-1][j][k]=(sum[i-1][j][k+1]+dp[i-1][j][k])%mod;
        for(int k=n;k>=1;k--)
            for(int j=1;j<=k;j++)
            sum[i-1][j][k]=(sum[i-1][j][k]+sum[i-1][j-1][k])%mod;
        for(int j=1;j<=n;j++)
            for(int k=j;k<=n;k++)
                dp[i][j][k]=sum[i-1][j][k]%mod;
    }
    ll ans=0;
    for(int j=1;j<=n;j++)
        for(int k=j;k<=n;k++)
        ans=(ans+dp[m][j][k])%mod;
    printf("%lld\n",ans);
    return 0;
}

这样是可以过的,但是这就是o(3*n^2 m)了,看的别人还有(mn^2)的,前缀和处理的更加方便

    再记录一下组合数学的思路,其实把a数组放在b的后面可以发现这是一个递减序列,所以我们算出这个2m长度的递减序列有多少个就可以了,这2m个数都是1-n范围内的,那我们可以想成是把这2m个球放到n个盒子里有多少种方法,为了方便计算可以设成把2m+n个球放到n个盒子里每个盒子里至少有一个球,那么我们只需要把这2m+n分成n段就可以,想一下也就是把n-1个挡板把2m+n个球划分出来,2m+n个球因为两边不能放挡板所以有2m+n-1个缝隙,那就是2m+n-1个缝隙里放n-1个挡板,那就是C(2m+n-1,n-1)了

ll fac[2005];
ll C(ll a,ll b){return (fac[a]*getinv(fac[a-b])%mod)*getinv(fac[b])%mod;}
ll n,m;
int main(){
    fac[0]=1;
    for(ll i=1;i<=2000;i++) fac[i]=fac[i-1]*i%mod;
    cin>>n>>m;
    ll ans=C(2*m+n-1,n-1);
    cout<<ans<<"\n";
    return 0;
}

Letter Picking 博弈 区间dp

dp[i][j]表示s[i]~s[r]这段子串内的情况,dp[i][j]=0表示平局,1表示Alice赢,因为一开始初始化的时候对于长度为2的字符串我们是一定可以知道结果的,不就是平局就是Alice赢,所以从长度为2的状态转移出来的也一定不会有别的情况;假设要讨论s[i][j]这个情况,先手拿走s[i],那么后手可以拿s[i+1],如果s[i+2][j]=1,那么一定是先手赢,如果s[i+2][j]=0,那么如果s[i]<s[i+1]也一定是先手赢,别的情况也可以如此考虑

Educational Codeforces Round 135 (Rated for Div. 2) A - E - 知乎 (zhihu.com)

ll t,dp[2005][2005];
char s[2005];
int main(){
    scanf("%lld",&t);
    while(t--){
        scanf("%s",s+1);
        ll n=strlen(s+1);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) dp[i][j]=0;
        for(int i=1;i<n;i++)
            if(s[i]!=s[i+1]) dp[i][i+1]=1;
            else dp[i][i+1]=0;
        for(int len=3;len<n;len+=2)
        for(int i=1;i+len<=n;i++){
            ll j=i+len;
            if((dp[i][j-2]||s[j]<s[j-1])&&(dp[i+1][j-1]||s[j]<s[i])) dp[i][j]=1;
            else if((dp[i+2][j]||s[i]<s[i+1])&&(dp[i+1][j-1]||s[i]<s[j])) dp[i][j]=1;
            else dp[i][j]=0;
        }
        if(dp[1][n]) printf("Alice\n");
        else printf("Draw\n");
    }
    return 0;
}

P6862 [RC-03] 随机树生成器 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 概率 线性求逆元

每个n的情况数是(n-1)!,对于一个p来说他的度数由他的父亲节点贡献一条(1除外),其他的都是孩子p1来贡献,对于p1来说p1去连p的概率是1/(p1-1),所以p的贡献就等于

(n-1)!(sum(1/(p1-1))+(n-1)!(p是否是1)  p1表示p的所有孩子

乘法逆元 线性求逆元 

P6862 题解 - 一只书虫仔の小窝 - 洛谷博客

ll t,n,k,fac[10000007],inv[10000007];
ll sum[10000007];
int main(){
    fac[0]=1;sum[0]=0;inv[1]=1;
    for(ll i=2;i<=10000000;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(ll i=1;i<=10000000;i++) fac[i]=fac[i-1]*i%mod;
    sum[1]=1;
    for(ll i=2;i<=10000000;i++) sum[i]=(sum[i-1]+inv[i])%mod;
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&k);
        ll p=(sum[n-1]-sum[k-1]+(k!=1)+mod)%mod;
        //cout<<p<<" "<<fac[n-1]<<endl;
        ll ans=fac[n-1]*p%mod;
        printf("%lld\n",ans);
    }
    return 0;
}