土拨鼠猎人 数位DP

问题描述
Sparrow是一只土拨鼠。

作为一只可爱又机智的土拨鼠,Sparrow常常被土拨鼠猎人们盯上。众所周知,土拨鼠很擅长打洞,他把每个洞按照1,2,3,4,……的顺序从小到大编号。为了迷惑土拨鼠猎人,Sparrow只选择了一些编号特殊的洞作为居住地。这些特殊的洞的编号满足以下几个条件:

1.不含前导零。

2.如果位数大于等于三位,则不存在连续三位数字构成“233”。如“15233”,“23345”就不是特殊编号的洞。

3.如果位数大于等于三位,则不能同时满足存在“连续两位数字相等”和“某一个数字恰好等于它右边的数字加一”。如“13396”和“25481”满足条件,而“65377”则不行。

4.特别地,1~99不足三位,它们也是特殊的编号。

现在,天才土拨鼠猎人MLX得到可靠情报,Sparrow藏在编号第N小的特殊的洞里。你能告诉MLX这个洞的编号吗?

解:
记忆化搜索好啊!
定义f[i][j][pr][r1][r2][bj][d] 表示第i+1位填j i+2位为pr r1表示是否有相同 r2 表示是否有递减 d表示是否有前导0

关键是是否有前导0 这个需要特判!

采用记忆化搜索解数位DP 更好更快
code:

//
#include<bits/stdc++.h>
#define ll long long
#define maxnn 40
ll f[maxnn][20][20][2][2];
using namespace std;
ll n;
vector<ll > num;
ll dfs(int i,int j,int pr,int r1,int r2,int bj,int d) {
	if(i<=-1) return 1;
	ll ans=0;
	if((~f[i][j][pr][r1][r2])&&(!bj)) return f[i][j][pr][r1][r2];
	int minn;
	minn=(bj?num[i]:9);
	if(r1==1&&r2==1) {
		return 0;
	}
	int fla=0;
	for(int k=0; k<=minn; k++) {
		if(d&&k!=0) fla=0;
		else
		{
			if(d&&(k==0)) fla=1;
			else
			{
				if((!d)) fla=0;
			}
		}
		if(k==3&&pr==2&&j==3) {
			continue;
		}
		if(j==k+1&&(!d)) {
			if(r2==1) {
				continue;
			} else {
				ans+=dfs(i-1,k,j,1,0,(k==minn)&bj,fla);
				continue;
			}
		} else if(j==k&&(!d)) {
			if(r1==1) {
				continue;
			} else {
				ans+=dfs(i-1,k,j,0,1,(k==minn)&bj,fla);
				continue;
			}
		}
		else
		ans+=dfs(i-1,k,j,r1,r2,(k==minn)&bj,fla);
	}
	if(!bj) f[i][j][pr][r1][r2]=ans;
	return ans;
}
ll isok(ll t) {
	memset(f,-1,sizeof(f));
	num.clear();
	while(t) {
		num.push_back(t%10);
		t/=10;
	}
	return dfs(num.size()-1,-2,-2,0,0,1,1)-1;
}
int main() {
	//freopen("tes","r",stdin);
	//freopen("myp.out","w",stdout);
	cin>>n;
	ll l=0,r=1e18;
	while(l<=r) {
		ll mid=(l+r)>>1;
		if(isok(mid)>=n) {
			r=mid-1;
		} else {
			l=mid+1;
		}
	}
	cout<<l;
}
原文地址:https://www.cnblogs.com/OIEREDSION/p/11734914.html