Brief Intro:
将所有n位二进制串中满足字典序不大于其逆序串,取反串,逆序取反串中按字典序排序的第m个输出
n<=50
Algorithm:
首次接触数位DP的题目
根据数据范围,我们不能枚举。从而只能一位一位确定
由于要求字典序第m大的,我们可以通过求出如果当前位为0的个数来确定该位为0还是1
接下来就是典型的数位DP了
数位DP其实也就是一个记忆化的过程,同时在状态中用变量对上界加以标识
此题中即为dp[cur][rev][inv],后两位表示是否达到反串上界和逆串上界
Code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll n,k,dp[55][2][2],res[55]; ll solve(ll cur,ll rev,ll inv) { if(dp[cur][rev][inv]>-1) return dp[cur][rev][inv]; //记忆化 if(cur*2>=n) return dp[cur][rev][inv]=1; ll &ret=dp[cur][rev][inv]=0; for(int i=0;i<2;i++) if(res[cur+1]==i || res[cur+1]==-1) for(int j=0;j<2;j++) if(res[n-cur]==j || res[n-cur]==-1) if((i==j || 2*cur+1!=n) && (!rev || i<=j) && (!inv || i<=1-j)) ret+=solve(cur+1,rev&(i==j),inv&(i!=j)); return ret; } int main() { cin >> n >> k;k++; memset(dp,-1,sizeof(dp)); memset(res,-1,sizeof(res)); if(solve(0,1,1)<k) return cout << -1,0; for(int i=1;i<=n;i++) { memset(dp,-1,sizeof(dp)); res[i]=0; ll t=solve(0,1,1); //判断该位的值 if(t<k) k-=t,res[i]=1; cout << res[i]; } return 0; }
Review:
1、对于求第k个满足要求的数时
可以通过 已知前缀枚举满足要求的个数+逐位确定每一位 的方法来取代枚举
2、数位DP的特殊点仅仅在对上界状态的维护,其余和记忆化搜索相同