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

本文介绍了一种求解特定数字组合形成的数的总和问题的高效算法,并通过多种优化手段将其从初始的暴力解法逐步改进至最终的可行解法。文章详细解释了如何利用动态规划减少计算复杂度,并通过调整计算顺序进一步优化性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;

}

 

 

# T617559 「TPOI-5A」Luminescence ## 题目背景 ![](https://cdn.luogu.com.cn/upload/image_hosting/ownsj515.png) (图片来自 Phigros 曲绘,侵删。) ## 题目描述 给定 $n$ 与两个长度为 $n$ 的序列 $a,b$。定义一个 $0\sim n-1$ 的排列 $q$ 是 **魔怔的**,当且仅当: - $\forall 1\le k\le n,\min^k_{i=1}q_i=a_k$。 - $\forall 1\le k\le n,\min^n_{i=k}q_i=b_k$。 定义一个排列 $q$ 的权值为 $\sum_{1\le l\le r\le n}\operatorname{mex}_{l\le i\le r}q_i$,所有魔怔的排列的权值之和。答案对 $998244353$ 取模。 一个集合 $M$ 的 $\operatorname{mex}(M)$ 定义为最小的没有在 $M$ 中出现的自然数。如 $\text{mex}\{1,2,3,4\}=0,\text{mex}\{0,1,3,4\}=2$。 ## 输入格式 本题有多组测试数据。对于每个测试点,先输入一个正整数 $T$,表示数据组数。 对于每一组测试数据,第一行一个正整数 $n$, 第二行输入 $n$ 个整数 $a_1,a_2,\dots,a_n$,第三行 $n$ 个整数 $b_1,b_2,\dots,b_n$。 ## 输出格式 对于每一组测试数据,输出一行一个整数,表示所有魔怔的排列的权值之和。 ## 输入输出样例 #1 ### 输入 #1 ``` 3 4 0 0 0 0 0 1 2 3 4 1 0 0 0 0 0 2 2 4 0 0 0 0 0 1 1 1 ``` ### 输出 #1 ``` 10 11 14 ``` ## 说明/提示 |$\text{Subtask}$|$n\le$|$\sum n\le$|分值| |:-:|:-:|:-:|:-:| |$1$|$8$|$800$|$20$| |$2$|$10^3$|$10^4$|$40$| |$3$|$2\times10^5$|$2\times10^6$|$40$| 对于 $100\%$ 的数据,$1 \le n \le 2 \times 10^5,0 \le a_i,b_i < n, \sum n \le 2 \times 10^6$。 **对于每组数据保证存在至少一个魔怔的排列。** c++,不用vector,注释
最新发布
08-09
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值