2023湖南省第十九届大学生计算机程序设计竞赛 ---hard math

直接进入正题

题目中的f(x) 表示 数字x中非重复的位数,例如f(10112)=3 ,非重复的位数分别是 1,0,2共3个。

很明显这是一道数位dp题 当时太菜了没做出来        不懂数位dp的可以先去自行了解一下

注意到题给的L,R非常大,可能有200000位数!! longlong都得爆到火星去 所以我们要用2个数组去分别记录L,R

然后我们分别处理出   

[0,L] 满足题意的答案记为 ans1     

[0,R] 满足题意的答案记为 ans2

这样答案为 ans2-ans1 类似前缀和求区间和 注意!!题目要求的区间是[L,R],而  ans2-ans1 表示的是区间[L+1,R],所以我们要再特判一下L,这样就满足题意了

下面放 呆麻主食        也是800ms艰险拿下

#include <bits/stdc++.h>
#define int long long
#define ll long long
#define no cout << "NO" << endl
#define yes cout << "YES" << endl
#define endl "\n"
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int l,r,n,A;
int a[2][200005];//a[0][]用来记录L,a[1][]用来记录R 
int f[200005][20];//dp数组  f[i][j] 表示到第i位且此时非重复的位数为j时的答案
int kk[20];//kk[i]表示i出现了几次
int mod=1e9+7;//题目要求的取模
int dfs(int x,int sum,int falt,int dd,int k){
	//x为目前遍历到哪一位
	//sum表示此时非重复的位数
	//falt为1时表示有上界限制
	//dd为1时表示有前导0
	//k=0时表示此时求得是L,k=1时表示此时求得是R
	
	if(x>n){//遍历完了 退出
		return sum==A;
	}
	
	//记忆化 利用已计算出的答案 用于提速
	if(!falt&&!dd&&f[x][sum]!=-1) return f[x][sum];
	
	int mx=9;//此时这一位的上界
	if(falt) mx=a[k][x];//有上界限制为a[k][x] 无上界限制为9
	
	int res=0;
	for(int i=0;i<=mx;i++){//遍历这一位的所有取值
		
		if(dd&&i==0){//前面是前导0且这一位也为0,无贡献,直接继续往下dfs
			res+=dfs(x+1,sum,falt,dd,k);
			res=res%mod;//注意取模
		}else{
			kk[i]++;//此时选i
			int nm=0;
			if(kk[i]==1) nm=1;//如果i第一次被选
			res+=dfs(x+1,sum+nm,falt&&(i==mx),dd&&i==0,k);
			res=res%mod;//注意取模
			kk[i]--;//回溯置0
		}
	}
	
	//无限制条件才可放入,对应上面的记忆化
	if(!falt&&!dd) f[x][sum]=res;
	
	return res;
}

int find(int k){
	int ans=0;
	memset(&f,-1,sizeof f);//记得初始化
	
	for(int i=0;i<=a[k][1];i++){//枚举第一位
		kk[i]=1;
		ans=(ans+dfs(2,1,i==a[k][1],i==0,k))%mod;
		kk[i]=0;
	}
	return 	ans;
}
signed main(){
	FAST
	cin>>n;
	char c;
	for(int i=1;i<=n;i++){//输入L
		cin>>c;
		a[0][i]=c-'0';
	}
	for(int i=1;i<=n;i++){//输入R
		cin>>c;
		a[1][i]=c-'0';
	}
	cin>>A;
	int ans=0,sum=0;
	for(int i=1;i<=n;i++){//特判L是否满足
		kk[a[0][i]]++;
		if(kk[a[0][i]]==1){
			sum++;
		}
	}
	if(sum==A) ans++;
	for(int i=0;i<=9;i++) kk[i]=0;
	
    //ans2-ans1+特判L   恭喜你成功ac
	cout<<(find(1)-find(0)+ans+mod)%mod<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值