Atcoder Regular 058 E - 和風いろはちゃん / Iroha and Haiku - 状压

题目大意:问有多少长度为N的数列{aN},满足每个数字权值在[1,10]中,并且不存在1<=x< y< z< w<=N, 使得S[x,y)=X,S[y,z)=Y, S[z,w)=Z。其中X, Y, Z是给定的正整数,S[x,y)表示a[x]+a[x+1]+…+a[y-1]。
N<=40, X<=5, Y<=7, Z<=5 。
题解:如果n小一点就可以容斥了;可以直接dp,但是会有重复计数,而且很难去重,于是显然去考虑问题的反面,即有多少序列不包含(X,Y,Z)?
我们把问题做这样一个转化, 即把数字i看成一个二进制数,其中第i位是1。例如1看成1,2看成10,3看成100,5卡成10000.这样有什么好处呢?好处在于,例如,(2,3)会被看作10100,而5被看作10000,10100包含了10000.可以推广证明,如果一列数的末尾是(X,Y,Z)的话,那么看成二进制后就会包含(X,Y,Z)转化的二进制数。这样设dp[i][j]表示前i个数字看成的二进制数字的最后17位(因为x+y+z<=17)是j的方案数,最后从总数10^n中减去不合法的即可。
代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#define lint long long
#define LOG 17
#define maxK 10
#define V 1<<LOG
#define N 42
#define mod 1000000007
using namespace std;
inline int updv(int x,int p) { return x|(1<<(p-1)); }
int dp[N][V],can[V];
int fast_pow(int x,int k)
{
    int ans=1;
    while(k)
    {
        if(k&1) ans=(lint)ans*x%mod;
        x=(lint)x*x%mod,k>>=1;
    }
    return ans;
}
int show(int x)
{
    for(int i=1;i<=LOG;i++)
        printf("%d",(x&(1<<(i-1)))>>(i-1));
    cout<<endl;
    return 0;
}
int main()
{
    int n,x,y,z;scanf("%d%d%d%d",&n,&x,&y,&z);
    int all=(1<<LOG)-1,v=0,ans=0;dp[0][0]=1;
    v=updv(v,x+y+z),v=updv(v,y+z),v=updv(v,z);
    for(int i=0;i<=all;i++) can[i]=((i&v)!=v);
    for(int i=0,x;i<n;i++)
        for(int j=0;j<=all;j++)
            if(can[j]&&(x=dp[i][j]))
                for(int k=1;k<=maxK;k++)
                    (dp[i+1][all&updv(j<<k,k)]+=x)%=mod;
    for(int i=1;i<=all;i++)
        if(can[i]) (ans+=dp[n][i])%=mod;
    printf("%d\n",(fast_pow(10,n)-ans+mod)%mod);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值