P10958 启示录

P10958 启示录

前置知识

数位 dp 或者记忆化 dfs(不会的看 oiwiki 链接)。

思路

记忆化 dfs 枚举每一位数,dfs 有 \(6\) 个参数 int dfs(int pos,int p1,int p2,bool f,bool fl,bool f666),分别是当前位数、前一位、前两位、是否可以无限制放置、是否在放置前导零、是否已经达成目标。在记忆化过程中只保留 \(f\gets 1\) 的情况就可以了。然后在往下递归时注意更新三个布尔值和前两位的数就可以了。也就是在 \(i<num_{pos}\) 时使 \(f\gets1\),使 \(fl\gets fl|i\),在达成 \(i=p_1=p_2=6\) 时使 \(f666\gets1\) 就可以了。这样我们就得到了求所有小于 \(x\) 的魔鬼数总数量的函数 \(js(x)\) 了。然后题目要求的是第几个魔鬼数是什么,我们只需要一个二分:如果 \(js(mid)<a\) 那么 \(l\gets mid+1\),否则 \(r\gets mid\)。思路会了代码就很简单了。

ACcode

#include <bits/stdc++.h>
using namespace std;
#define freop(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout);
#define int long long
#define double long double
#define re register
#define endl '\n'
#define inf 0x7f7f7f7f7f7f7f7f
#define lowbit(x) (x&-x)
#define pii pair<int,int>
#define fir first
#define sec second
#define umap unordered_map
#define uset unordered_set
const int N=30,M=15;
int T,a,tot,vis[N][M][M][2],num[N];

bool c(int p1,int p2,int x){return !(x^6||p1^6||p2^6);}
inline int dfs(int pos,int p1,int p2,bool f,bool fl,bool f666){
	if(!pos) return f666;
	int sum=vis[pos][p1][p2][f666];
	if(f&&sum>=0) return sum;
	else{
		sum=0;
		int mx=(f?9:num[pos]);
		for(int i = 0;i<=mx;++i){
 			sum+=dfs(pos-1,i,p1,f|(i<num[pos]),fl|i,c(p1,p2,i)|f666);
		}
	}
	if(f) vis[pos][p1][p2][f666]=sum;
	return sum;
}
inline int js(int x){
	tot=0;
	while(x) num[++tot]=x%10,x/=10;
	return dfs(tot,10,10,0,1,0);
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	memset(vis,-1,sizeof(vis));
//	freop();
//↑以上为初始化↑
//------------------------------
	cin >> T;
	while(T--){
		cin >> a;
		int l=666,r=1145141919810,mid;
		while(l<r){
			mid=(l+r)>>1;
			if(js(mid)>=a) r=mid;
			else l=mid+1;
		} 
		cout << l << endl;
	}
	return 0;
}
posted @ 2026-10-21 21:25  elon_giao  阅读(3)  评论(0)    收藏  举报