AcWing310 启示录(数位dp)

这道题因为要求的是第k大的魔鬼数

所以我们可以采用二分的方法,用k-1来二分,这样就可以找到最左边的>=k的,也就是第k大

之后是数位dp,题目要求的是666,因此我们考虑用两位表示前一位填p1,前前位填p2。剩下的还要保存在这个数之前是否已经存在魔鬼数,这便与边界判断数是否成立,还有一维是老套路,看是否是最大的

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
vector<int> num;
int f[20][2][2][2][2];
int k;
int dfs(int cur,int p2,int p1,int m,int flag){
    if(cur==(int)num.size())
    return m==1;
    if(f[cur][p2][p1][m][flag]!=-1)
    return f[cur][p2][p1][m][flag];
    int v=9;
    if(flag)
    v=num[cur];
    int i;
    long long ans=0;
    for(i=0;i<=v;i++){
        ans+=dfs(cur+1,p1,i==6,m||(p1&&p2&&(i==6)),flag&&(i==v));
    }
    return f[cur][p2][p1][m][flag]=ans;
}
int check(ll n){
    num.clear();
    while(n){
        num.push_back(n%10);
        n/=10;
    }
    reverse(num.begin(),num.end());
    memset(f,-1,sizeof f);
    int ans=dfs(0,0,0,0,1);
    return ans<=k;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        cin>>k;
        k--;
        ll l=0,r=1e10;
        while(l<r){
            ll mid=(l+r)>>1;
            if(check(mid))
            l=mid+1;
            else
            r=mid;
        }
        cout<<l<<endl;
    }
}
View Code
原文地址:https://www.cnblogs.com/ctyakwf/p/12697482.html