数位dp复健

对于数位dp,有个挺正常的套路。

首先一般拆分数字。将数字的每一位拿出来,作为上界。

然后设置状态,一般是填到第几位,然后根据题目设置,比如说windy就要看上一位是谁,不要62就看上一位是不是6之类的。

然后注意状态不能跟数字到底是谁有关。就是要确保后面可以乱放的情况下设置状态。

拆分完后从高位开始处理。一位一位,用limit来看是否能取到9,反之取到对应数位。然后加起来即可。

因为记忆化的原因,复杂度很小。

windy数

很水,但又前导0的限制,如果一直没有数,直到个位直接返回9。同时前后两位差绝对值>=2。然后走就行。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}
	return cnt*f;
}int a,b;
int dp[14][12];
int cnt,num[123];
int dfs(int pos,int pre,int lead,int limit){
	//cout<<pos<<" "<<pre<<" "<<lead<<" "<<limit<<endl;
	if(pos==0)return 1;
	if(!limit&&!lead&&dp[pos][pre]!=-1)return dp[pos][pre];
	int tmp=0,up=limit?num[pos]:9;//cout<<up<<endl;
	if(pos==1&&lead)return 9;
	for(int i=0;i<=up;i++){
		if(abs(pre-i)<2&&!lead)continue;
		if(i==0&&lead)tmp+=dfs(pos-1,0,lead,limit&&i==num[pos]);
		else tmp+=dfs(pos-1,i,0,limit&&i==num[pos]);
	}if(!lead&&!limit)dp[pos][pre]=tmp;return tmp;
}
int solve(int x){
	if(x<10)return x;
	int cnt=0;
	while(x){
		num[++cnt]=x%10;x/=10;
	}//cout<<cnt<<endl;
	return dfs(cnt,0,1,1);
}
int main(){
	memset(dp,-1,sizeof(dp));
	a=in;b=in;
	printf("%d\n",solve(b)-solve(a-1));
	return 0;
}

不要62

只要不是4,前面是6这位不是2两个都满足就行。直接走。

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
int dp[14][2];
int num[12],cnt;
int dfs(int pos,int is6,int limit){
	if(pos==0)return 1;
	if(!limit&&dp[pos][is6]!=-1)return dp[pos][is6];
	int tem=0,up=limit?num[pos]:9;
	for(int i=0;i<=up;i++){
		if(i==4)continue;
		if(is6&&i==2)continue;
		tem+=dfs(pos-1,i==6,limit&&i==num[pos]);
	}if(!limit)dp[pos][is6]=tem;return tem;
}
int solve(int x){
	cnt=0;
	while(x){
		num[++cnt]=x%10;x/=10;
	}return dfs(cnt,0,1);
}
int main(){
	memset(dp,-1,sizeof(dp));
	int a,b;a=in;b=in;
	cout<<solve(b)-solve(a-1);
	return 0;
}

优美的数字

首先二分出一个答案转为判定性问题。然后求1~x中符合条件的数的个数。

我们怎么确定一个数包含否子串呢?直接AC自动机啦。

设状态dp[pos][u][flag]表示当前在第几位,在AC自动机上的几号结点,是否已经包含子串。

然后每次走就是了。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
struct node{
	int ch[10],key,fail;
}t[3003];int tot;
char ch[2300];
int l,r,k,n;
void insert(char s[],int len){
	int rt=0;
	for(int i=1;i<=len;i++){
		int x=s[i]-'0';
		if(!t[rt].ch[x]){
			t[rt].ch[x]=++tot;
		}rt=t[rt].ch[x];
	}t[rt].key=1;
}
queue<int> q;
void getfail(){
	for(int i=0;i<10;i++){
		if(t[0].ch[i])q.push(t[0].ch[i]);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<10;i++){
			if(t[u].ch[i]){
				t[t[u].ch[i]].fail=t[t[u].fail].ch[i];q.push(t[u].ch[i]);
				t[t[u].ch[i]].key|=t[t[t[u].ch[i]].fail].key;
			}else{
				t[u].ch[i]=t[t[u].fail].ch[i];
			}
		}
	}
}
int num[23],cnt;
int dp[23][1503][2];
int dfs(int pos,int u,int flag,int limit,int lead){
	if(pos==0)return flag?1:0;
	if(!limit&&!lead&&dp[pos][u][flag]!=-1)return dp[pos][u][flag];
	int tem=0,up=limit?num[pos]:9;
	for(int i=0;i<=up;i++){
		if(lead&&i==0&&pos>1){
			tem+=dfs(pos-1,0,flag,limit&&i==num[pos],1);
		}else tem+=dfs(pos-1,t[u].ch[i],flag|t[t[u].ch[i]].key,limit&&i==num[pos],lead&&i==0);
	}
	if(!limit&&!lead)dp[pos][u][flag]=tem;return tem;
}
int check(int x){//cout<<x<<" ";
	cnt=0;memset(num,0,sizeof(num));
	while(x){
		num[++cnt]=x%10;x/=10;
	}memset(dp,-1,sizeof(dp));
	int ans=dfs(cnt,0,0,1,1);
	//cout<<ans<<endl;
	return ans;
}
signed main(){
	l=in;r=in;k=in;n=in;
	for(int i=1;i<=n;i++){
		scanf("%s",ch+1);int len=strlen(ch+1);
		insert(ch,len);
	}getfail();
	//for(int i=1;i<=tot;i++)cout<<t[i].key<<" ";cout<<endl; 
	int L=l,R=r+1,kk=check(l-1);
	//cout<<kk<<endl;
	while(L<R){
		int mid=(L+R)>>1;
		if(check(mid)-kk>=k)R=mid;
		else L=mid+1;
	}if(L==r+1)cout<<"no such number";
	else cout<<L;
	return 0;
}

Balanced Number

暴力搞吧。直接枚举哪一位是力矩,因为这一位是力矩,下一位就肯定不是。所以不重不漏。

注意有负数,整体挪动3000位,反正开的下。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar(); 
	}return cnt*f;
}
int f[20][20][6000];int num[20],cnt;
int dfs(int pos,int mid,int sum,int lead,int limit){
	if(!pos)return (sum==3000&&!lead);
	if(!limit&&!lead&&f[pos][mid][sum]!=-1)return f[pos][mid][sum];
	int tem=0,up=limit?num[pos]:9;
	for(int i=0;i<=up;i++){
		tem+=dfs(pos-1,mid,sum+(pos-mid)*i,lead&i==0,limit&(i==num[pos]));
	}if(!limit&&!lead)f[pos][mid][sum]=tem;
	return tem;
}
int solve(int x){
	if(x<=0)return x;
	cnt=0;
	while(x){
		num[++cnt]=x%10;x/=10;
	}
	int ans=0;
	for(int i=1;i<=cnt;i++){
		ans+=dfs(cnt,i,3000,1,1);
	}return ans;
}
signed main(){
	int t=in;memset(f,-1,sizeof(f));
	while(t--){
		int a=in;int b=in;
		cout<<solve(b)-solve(a-1)<<'\n';
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值