Just Sum It [UvaLive 5063] DP+组合数

TLE到死,加了n多优化才过,泪流满面.

题意:

  给你a1,a2..a9,ai表示有ai个i,问用这些数字组成的不同的数的和MOD 1000000007以后是多少.

题解:

  首先方法显然:

       枚举位数,枚举某个数在哪个位上,假设有l位,数i在第j位上,则i在这个位上贡献的值为i*10^(j-1)*(剩下的数排列成l-1位的不同排列个数).

       在所有不同的位数下,所有数在不同位置上贡献的值求和即是所求结果.

然后难点是如何求:剩下的数排列成l-1位的不同排列个数

   暴力:枚举每个数用了几个,如sigma(pi)==l-1,则结果为(l-1)!/((p1!)*(p2!)*..(pi!)),最坏10^10次,必然TLE

   DP:

        dp[i][j]表示用了1..i-1这些数构成j位的排列数.

        状态方程:

             dp[i+1][j+k]+=dp[i][j]*C(j+k,k) (0=<k<=ai)

改成DP后本以为能A了,却还是TLE.

优化1:

仔细观察下可以发现,在位数确定的情况下,每个数不管在哪个位上,(剩下的数排列成l-1位的不同排列个数)不变,比方说6XX,X6X,XX6,这两个XX排列个数是确定的,所以可以直接666*(XX的排列个数)

优化2:

在加了优化1后还是T,发现算上面的666还可以优化,666=6*111,可以先预处理出不同长度的1..1的值,到时候直接乘之

优化3:

仍然T,改变一下枚举顺序,先枚举数字,再枚举长度,结果完全等价,但是可以先把改数字用过一次后剩下的数的排列的dp值算出来了,而不用再在每位的时候算一次了,最后复杂度为O(9*9*9)

估计这题数据组数较多,时间卡的也太紧了.

代码:

#include <cstdlib>

#include <stdio.h>

#include <iostream>

#include <memory.h>

#include <stdio.h>

using namespace std;

#define MOD 1000000007

long long extended_gcd(long long a,long long b,long long &k,long long &t)

{

  if (b==0)

  {

   k=1;

   t=0;

   return a;

 

  }

  else

  {

    long long tp_gcd;

    tp_gcd=extended_gcd(b,a%b,k,t);

    long long temp;

    temp=k;

    k=t;

    t=temp-(a/b)*t;

    return tp_gcd;

  }

}

long long in[1000];

long long inv(long long x)

{

    long long k,t;

    if (in[x]!=-1) return in[x];

    extended_gcd(x,MOD,k,t);

    while(k<0) k+=MOD;

    while(k>=MOD) k-=MOD;

    in[x]=k;

    return k;

}

int a[20];

long long dp[20][100];

long long Cv[200][200];

long long p11[200];

long long C(int n,int x)

{

    if (Cv[n][x]!=-1) {return Cv[n][x];}

    long long ans=1;

    int i,j;

    for(i=1;i<=n;i++)

        ans=ans*i%MOD;

    for(i=1;i<=x;i++)

        ans=ans*inv(i)%MOD;

    for(i=1;i<=n-x;i++)

        ans=ans*inv(i)%MOD;

    Cv[n][x]=ans;

    return ans;

}

long long cal_dp()

{

    memset(dp,0,sizeof(dp));

    int i,j;

    dp[1][0]=1;

    for(i=1;i<=9;i++)

        for(j=0;j<=90;j++)

        {

            long long tmp=dp[i][j];

            if (tmp==0) continue;

            for(int k=0;k<=a[i];k++)

                dp[i+1][j+k]=(dp[i+1][j+k]+Cv[j+k][j]*tmp%MOD)%MOD;

        }

}

int main(int argc, char** argv)

{

    long long tcase,i,j,sum,ans;

    memset(in,-1,sizeof(in));

    scanf("%lld",&tcase);

    for(i=0;i<=100;i++)

        for(j=0;j<=100;j++)

            Cv[i][j]=-1;

    for(i=0;i<=100;i++)

        for(j=0;j<=100;j++)

            C(i,j);

    p11[1]=1;

    for(i=2;i<=100;i++)

        p11[i]=(p11[i-1]*10+1)%MOD;

    while(tcase--)

    {

        sum=0;

        memset(a,0,sizeof(a));

        for(i=1;i<=9;i++)

        {

            scanf("%d",&a[i]);

           sum+=a[i];

        }

        ans=0;

        for(i=1;i<=9;i++) //第几个

              if (a[i]>0) 

             {

                 a[i]--;

                 cal_dp();

               for(int l=1;l<=sum;l++) //位数

               {

                  long long t=0;

                  t=i*p11[l];  

                  long long ddd=dp[10][l-1];

                  ans=(ans+t*ddd%MOD)%MOD;

                //  if (ans<0) ans+=MOD; 

               }

                 a[i]++;

        }

       // cout<<ans<<endl;

        printf("%lld/n",ans);

    }

    return 0;

}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值