HDU 5519 Kykneion asma

9 篇文章 0 订阅

题目链接

题目大意

给定一个 n n 和一个序列ai(0i4)求满足下列条件的 n n 位数个数。

  • 没有5.6.7.8.9

    • 0.1.2.3.4 0.1.2.3.4 的个数不超过 ai a i
    • 109+7 10 9 + 7 取模

      思路

      感觉很像是生成函数题。
      但是据说生成函数做法需要FFT。weila

      那就换一种方式考虑。考虑容斥。
      用总的方案数减去有一个数超的方案数加上两个数超的方案数
      那么怎么求这有些数超的方案数呢?考虑DP。
      f[i][j][S] f [ i ] [ j ] [ S ] 表示前 i i 个数,S状态对应的数都超了,有 j j 个数最后要超,可以有前导零的方案数。
      转移:f[i][j][S]=f[i1][j][S](5j+__builtin_popcount(S))+S(1<<k)f[ia[k]1][j][S(1<<k)](i1a[k])
      最后处理前导零:先对原来的 a a 数组做一遍,再将a[0],再做一遍,减去这第二次做的即可。

      代码
      #include<cstdio>
      #include<cstring>
      #include<string>
      #include<algorithm>
      #include<iostream>
      #include<cmath>
      #include<cstdlib>
      #include<ctime>
      #include<map>
      #include<queue>
      #include<vector>
      #include<stack>
      #include<set>
      #include<cctype>
      #define pa pair<int,int>
      #define INF 0x3f3f3f3f
      #define inf 0x3f
      #define fi first
      #define se second
      #define mp make_pair
      #define ll long long
      #define ull unsigned long long
      #define pb push_back
      
      using namespace std;
      
      inline ll read()
      {
          long long f=1,sum=0;
          char c=getchar();
          while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
          while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
          return sum*f;
      }
      const int MAXN=15010;
      ll f[MAXN][6][50];
      int a[5];
      #define Mod 1000000007
      ll quickpow(ll a,ll b)
      {
          ll ans=1;
          while (b)
          {
              if (b&1) ans*=a,ans%=Mod;
              b/=2,a*=a,a%=Mod;
          }
          return ans;
      }
      ll inv[MAXN],fac[MAXN];
      ll C(int n,int m)
      {
          return fac[n]*inv[m]%Mod*inv[n-m]%Mod;
      }
      int num[1<<6]; 
      ll work(int n)
      {
          memset(f,0,sizeof(f));
          for (int i=1;i<=5;i++)  
              f[0][i][0]=1;
          for (int j=1;j<=5;j++)
          {
              for (int i=1;i<=n;i++)
              {
                  for (int s=0;s<1<<5;s++)
                  {
                      if (num[s]>j) continue;
                      f[i][j][s]=f[i-1][j][s]*(5-j+num[s])%Mod;
                      for (int k=1;k<=5;k++)
                      {
                          if ((1<<(k-1)&s) && (i>=a[k]+1))
                              f[i][j][s]=(f[i][j][s]+f[i-a[k]-1][j][s-(1<<(k-1))]*C(i-1,a[k]))%Mod;
                      } 
                  }
              }
          }
          ll ans=quickpow(5,n);
          for (int s=1;s<1<<5;s++)
          {
              if (num[s]&1) ans=(ans-f[n][num[s]][s]+Mod)%Mod;
              else ans=(ans+f[n][num[s]][s]+Mod)%Mod;
          }
          return ans;
      }
      int main()
      {
          int T;
          scanf("%d",&T);
          fac[0]=1;
          for (int i=1;i<MAXN;i++)
              fac[i]=fac[i-1]*i%Mod;
          inv[MAXN-1]=quickpow(fac[MAXN-1],Mod-2);
          for (int i=MAXN-2;i>=0;i--)
              inv[i]=inv[i+1]*(i+1)%Mod;
          for (int i=0;i<1<<5;i++)
              for (int x=i;x;num[i]+=(x&1),x/=2);
          for (int t=1;t<=T;t++) 
          {
              printf("Case #%d: ",t);
              int n;
              scanf("%d",&n);
              for (int i=1;i<=5;i++)
                  scanf("%d",&a[i]);
              if (!a[1])
                  printf("%d\n",work(n));
              else
              {
                  ll ans=work(n);
                  a[1]--;
                  ans=(ans-work(n-1)+Mod)%Mod;
                  printf("%d\n",ans);
              }
          }
      }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值