HDU 5519 Kykneion asma(沈阳站K题&&DP+容斥)

这题想了好久没想出来,最后是学习的这里的做法解决的:K题题解

基于FFT,本弱到现在还是不会写,所以并不能提供FFT的写法。

分析:设 W(n,a0,a1,a2,a3,a4) 代表可以有前导 0 n 5 进制数字,至多ai个数字 i 的方案数。那么

ans=W(n1,a0,a11,a2,a3,a4)+W(n1,a0,a1,a21,a3,a4)+W(n1,a0,a1,a2,a31,a4)+W(n1,a0,a1,a2,a3,a41)

现在问题变成了如何求 W(n,a0,a1,a2,a3,a4) ,这里用容斥来求:
V(n,a0,a1,a2,a3,a4) 代表 n 5进制的数字, 0,1,2,3,4 中有数字超过规定限制的情况。

W(n,a0,a1,a2,a3,a4)=5nV(n,a0,a1,a2,a3,a4)

S(ab1,ab2..,abm) 代表数 b1,b2,..,bm 一定超过限制的方案数。

V(n,a0,a1,a2,a3,a4)=iS(ai)i,j,i,jS(ai,aj)+i,j,k,i,j,kS(ai,aj,ak)i,j,k,l,i,j,k,lS(ai,aj,ak,al)+S(a0,a1,a2,a3,a4)

dp[i][msk][MSK] 代表最终有状态 MSK 的数一定超过限制,当前 i 个数,有msk个数超过限制,这里可以 msk MSK 的子状态。

注意点:这里 MSK 中存在的数, msk 不存在的数,不能填入 i 位。

cnt[msk]代表这个状态下 1 的个数
这样得到

dp[i][msk][MSK]=dp[i1][msk][MSK](5cnt[MSK]+cnt[msk])+(msk$1<<k)>0dp[ia[k]1][msk|1<<k][MSK](i1a[k])

这里有一个东西: MSK 这个状态在表达式中只提供了一个 1 的个数,这个时候把MSK直接定义成最终有多少个数字超过了限制,最后一维设成 j

dp[i][msk][j]=dp[i1][msk][j](5j+cnt[msk])+(msk$1<<k)>0dp[ia[k]1][msk|1<<k][j](i1a[k])

此时:

S(ab1,ab2..,abm)=cnt[msk]=jdp[n][msk][j]

此时复杂度为 o(4n2552)

附上代码:

#include <bits/stdc++.h>
#include <algorithm>
#define LL long long
#define FOR(i,x,y)  for(int i = x;i < y;++ i)
#define IFOR(i,x,y) for(int i = x;i < y;-- i)

using namespace std;

typedef vector <int> VT;

const int maxn = 15010;
const int maxm = 30030;
const int Mod = 1e9+7;
const int MSK = 1<<5;

int n,a[5];

void gcd(LL a,LL b,LL& d,LL& x,LL& y){
    if(!b)  {d = a;x = 1;y = 0;}
    else{gcd(b,a%b,d,y,x);y -= x*(a/b);}
}

LL Inv(LL a,LL n){
    LL d,x,y;
    gcd(a,n,d,x,y);
    return d == 1 ? (x+n)%n : -1;
}

LL fac[maxn],inv[maxn],cnt[MSK];

void init(){
    fac[0] = 1; inv[0] = 1;
    FOR(i,1,maxn){
        fac[i] = i*fac[i-1]%Mod;
        LL p = Inv(i,Mod);
        inv[i] = inv[i-1]*p%Mod;
    }
    FOR(i,0,MSK)    cnt[i] = __builtin_popcount(i);
}

LL dp[maxn][MSK];
LL Pow;

LL C(LL p,LL q){
    return fac[p]*inv[q]%Mod*inv[p-q]%Mod;
}

LL quickpow(LL a,LL n,LL m){
    LL ans=1;
    while(n){
        if(n&1) ans = (ans*a)%m;
        a = (a*a)%m;
        n>>=1;
    }
    return ans;
}

void work(){
    LL ans = 0;
    FOR(p,1,5){
        if(!a[p])   continue;
        ans += Pow;
        ans %= Mod;
        -- a[p];
        -- n;
        FOR(j,1,6){
            memset(dp,0,sizeof(dp));
            dp[0][0] = 1;
            FOR(i,1,n+1){
                FOR(msk,0,MSK){
                    if(cnt[msk] <= j){
                        dp[i][msk] = dp[i-1][msk]*(5-j+cnt[msk])%Mod;
                        FOR(k,0,5){
                            if((msk & (1<<k)) && a[k] <= i-1){
                                dp[i][msk] += dp[i-a[k]-1][msk^(1<<k)]*C(i-1,a[k]);
                                dp[i][msk] %= Mod;
                            }
                        }
                    }
                }
            }
            FOR(msk,0,MSK){
                if(cnt[msk] == j){
                    if(j % 2){
                        ans -= dp[n][msk];
                        ans = (ans + Mod)%Mod;
                    }
                    else{
                        ans += dp[n][msk];
                        ans %= Mod;
                    }
                }
            }
        }
        ++ n;
        ++ a[p];
    }
    printf("%I64d\n",ans);
}


int main()
{
    //freopen("test.in","r",stdin);
    init();
    int T,tCase = 0;    scanf("%d",&T);
    while(T--){
        printf("Case #%d: ",++tCase);
        scanf("%d",&n);
        FOR(i,0,5)  scanf("%d",&a[i]);
        Pow = quickpow(5,n-1,Mod);
        work();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值