Hdu-5519 Kykneion asma(状压DP+容斥)

185 篇文章 0 订阅
116 篇文章 0 订阅
On the last day before the famous mathematician Swan's death, he left a problem to the world: Given integers  n n and  ai ai for  0i4 0≤i≤4, calculate the number of  n n-digit integers which have at most  ai ai-digit  i i in its decimal representation (and have no  5,6,7,8 5,6,7,8 or  9 9). Leading zeros are not allowed in this problem.
Input
There is one integer  T (1<T10) T (1<T≤10) in the beginning of input, which means that you need to process  T T test cases. In each test case, there is one line containing six integers representing  n n and  a0 a0 to  a4 a4, where  2n15000 2≤n≤15000 and  0ai30000 0≤ai≤30000.
Output
For each test case, you should print first the identifier of the test case and then the answer to the problem, module  109+7 109+7.
Sample Input
10
5 0 1 2 3 4
5 1 1 1 1 1
5 2 2 2 2 2
5 3 3 3 3 3
5 3 2 1 3 2
5 3 2 0 0 0
5 0 0 0 5 0
7000 41 2467 6334 2500 3169
7000 7724 3478 5358 2962 464
7000 5705 4145 7281 827 1961
Sample Output
Case #1: 535
Case #2: 96
Case #3: 1776
Case #4: 2416
Case #5: 1460
Case #6: 4
Case #7: 1
Case #8: 459640029
Case #9: 791187801

Case #10: 526649529

题意: 给你包括0的5个数字,又告诉你每个数字的最多使用次数,问你由这5个数字组成的不同n位数(无前导0)有多少个。

分析:这题可以直接上母函数+FFT,但是不能直接用NTT因为模数不是费马素数,然后在网上看到了还有这种容斥+状压DP的做法,容斥很好想,但是难点在怎么用状压DP求给定数字集超出限制的方案数,这里有一个小trick就是如果我们想让某个数字i超出限制,那么直接放入a[i]+1个数字就行了,f[i][mask][j]表示当前在第i位,当前 mask 这些数字超出了限制,且最终超出限制的数字为j个的方案数,那么有:

f[i][mask][j] = f[i-1][mask][j]*(5 - j + count(mask))+sigma(f[i-a[k]-1][mask xor (1<<k)][j]*C(i-1,a[k]))

加上f[i-1][mask][j]这部分很容易理解,相当于枚举第i位的数字,右边的部分相当于在此刻刚好超过a[k]这个限制时的方案数。

#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
typedef long long ll;
int T,Time,n,cnt[32],a[6];
ll jc[15005],inv[15005],f[15005][32][6];
void exgcd(ll a,ll b,ll &g,ll &x,ll &y)
{
    if(!b) g=a,x=1,y=0;
    else
    {
        exgcd(b,a%b,g,y,x);
        y-=a/b*x;
    }
}
ll Inv(ll a,ll n)
{
    ll d,x,y;
    exgcd(a,n,d,x,y);
    return d == 1 ? (x+n)%n : -1;
}
int lowbit(int x)
{
    return x & -x;
}
ll c(int x,int y)
{
    return (jc[x]*inv[y] % MOD)*inv[x-y] % MOD;
}
void add(ll &x,ll y)
{
    y %= MOD;
    x = (x + y) % MOD;
}
ll got(int n)
{
    memset(f,0,sizeof(f));
    for(int i = 1;i <= 5;i++) f[0][0][i] = 1;
    for(int j = 1;j <= 5;j++)
     for(int i = 1;i <= n;i++)
      for(int mask = 0;mask < 32;mask++)
      if(cnt[mask] <= j)
      {
         add(f[i][mask][j],f[i-1][mask][j] * (5 - j + cnt[mask]));
         for(int k = 1;k <= 5;k++)
         if(((1<<(k-1)) & mask) && i >= a[k] + 1) add(f[i][mask][j],f[i - a[k] - 1][mask - (1<<(k-1))][j]*c(i-1,a[k]));
      }
    ll temp = 1;
    for(int i = 1;i <= n;i++) temp = temp*5 % MOD;
    for(int mask = 1;mask < 32;mask++)
     if(cnt[mask] & 1) temp = (temp - f[n][mask][cnt[mask]] + MOD) % MOD;
     else temp = (temp + f[n][mask][cnt[mask]]) % MOD;
    return temp;
}
int main()
{
    jc[0] = inv[0] = 1;
    for(int i = 1;i <= 15000;i++) jc[i] = jc[i-1] * i % MOD,inv[i] = Inv(jc[i],MOD);
    for(int i = 1;i < 32;i++) cnt[i] = cnt[i - lowbit(i)] + 1;
    cin.sync_with_stdio(false);
    cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i = 1;i <= 5;i++) cin>>a[i];
        if(!a[1]) cout<<"Case #"<<++Time<<": "<<got(n)<<endl;
        else
        {
            int temp = got(n);
            a[1]--;
            temp = (temp - got(n-1) + MOD) % MOD;
            cout<<"Case #"<<++Time<<": "<<temp<<endl;
        }
    }
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值