DTOJ #4738. 迷惑数字统计

9 篇文章 0 订阅
7 篇文章 0 订阅

题目

首先定义什么是迷惑数字。

对于一个 2 n 2n 2n 位的数字 X X X,将其 随机排列 后划分成两个数字,它的前 n n n 位构成数字 A A A,后 n n n 位构成数字 B B B. 如果 A + B A+B A+B 10 10 10,则数字 X X X 是迷惑数字。注意 A A A B B B 可能有前导 0 0 0.

比如 46 46 46 是一个迷惑数字 ( 4 + 6 = 10 ) (4+6=10) (4+6=10) 9820 9820 9820 是一个迷惑数字 ( 98 + 02 = 100 ) (98+02=100) (98+02=100) 08362090 08362090 08362090 也是一个迷惑数字 ( 6020 + 3980 = 10000 ) (6020+3980=10000) (6020+3980=10000).

现在给你一个 2 n 2n 2n 位的数字,其中有些数位丢失了,丢失的数位用 ‘?’ 表示。你需要统计将这些问号替换成数字以获得迷惑数字的方案数。

数据范围

对于 10 % 10\% 10% 的数据,数字串长度 ≤ 20 \le 20 20. 问号最多 8 8 8 个。

对于 30 % 30\% 30% 的数据,数字串长度 ≤ 1 0 5 \le 10^5 105. 问号最多 8 8 8 个。

对于 100 % 100\% 100% 的数据,数字串长度 ≤ 1 0 5 \le 10^5 105. 问号最多 1000 1000 1000 个。

题解

考虑一下 a + b = 1 0 n a+b=10^n a+b=10n a , b a,b a,b 需要满足什么条件。
考虑加法的实质。我们将a,b逐位相加时,受到后面进位的影响最多只有 1 1 1,而当前位置相加最大值为 ( 9 + 9 + 1 ( 进 位 ) ) % 10 = 9 (9+9+1(进位))\%10=9 9+9+1()%10=9,不可能进位2次,所以要满足除了第一位是1,其他位都是0,就必须满足前面若干位加起来为 9 9 9,中间一位加起来为 10 10 10,后面若干位加起来位 0 0 0(即 0 + 0 0+0 0+0)。
所以 1 1 1 8 8 8 必须数量相等(匹配), 2 2 2 7 7 7 必须数量相等 … \dots 综上我们可以用dp计数来解决这道题: f [ i ] [ j ] f[i][j] f[i][j] 表示前i个匹配(即 ( 1 , 8 ) , ( 2 , 7 ) , ( 3 , 6 ) , ( 4 , 5 ) (1,8),(2,7),(3,6),(4,5) (1,8),(2,7),(3,6),(4,5),最多4个( i ≤ 4 i \leq 4 i4)),用 j j j 个问号,的合法匹配方案数,然后类似背包转移即可。

注意事项

由于 0 0 0 9 9 9处理起来比较麻烦,我们可以先枚举 0 0 0 9 9 9,再进行上述操作。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5+10;
const int M=1005;
const int P=1e9+7;
char ch[N];
ll m,f[7][M],c[M][M],cnt[15],ans;
int fu[N],tot[N];
int main(){
   scanf("%s",ch+1);
   int len=strlen(ch+1);
   for(int i=1;i<=len;++i){
   	if(ch[i]=='?')m++;
   	else tot[ch[i]-'0']++;
   }
   for(int i=0;i<=m;++i){
   	c[i][0]=1;
   	for(int j=1;j<=m;++j)c[i][j]=(c[i-1][j-1]+c[i-1][j])%P;
   }
   //cout<<"FAQ "<<m<<endl;
   for(int x=1,y=9;x<=5;++x,--y){
   	for (int i=1;i<=5;i++)for( int j=0;j<=m;j++)f[i][j]=0;
   	for(int i=0;i<=9;++i){
   		fu[i]=0;
   		cnt[i]=tot[i]-(i==x)-(i==y);
   		if(cnt[i]<0)fu[i]-=cnt[i],cnt[i]=0;
   	}
   	for(int x1=fu[0];x1<=m;++x1){
   		for(int x2=fu[9];x2+x1<=m;++x2){
   			int X1=x1+cnt[0],X2=x2+cnt[9]-fu[9];
   			if(X1>=X2&& !((X1-X2)&1))f[1][x1+x2]=(f[1][x1+x2]+c[m][x1]*c[m-x1][x2]%P)%P;
   		}
   	}
   //	for(int i=1;i<=m;++i)cout<<i<<" "<<f[1][i]<<endl;
   	for(int i=1,j=8;i<5;++i,--j){
   		int f1=(fu[i]+max(cnt[j]-cnt[i],1ll*0)),f2=(fu[j]+max(cnt[i]-cnt[j],1ll*0));
   		for(;f1+f2<=m;f1++,f2++){
   			for(int k=0;k+f1+f2<=m;++k)f[i+1][k+f1+f2]=(f[i+1][k+f1+f2]+f[i][k]*c[m-k][f1]%P*c[m-k-f1][f2]%P)%P;
   		}
   	}
   	ans=(ans+f[5][m])%P;
   }
   printf("%lld\n",ans);
   return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值