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;
}

浙公网安备 33010602011771号