题目链接:
点击打开题目
题意:
将所有二进制串(只允许前导
0
)中,同时满足字典序不小于其逆序串,取反串和逆序取反串(三种都要满足)的串提出来,按字典序排序,求第m 个。
2<=n<=50,1<=m<=1016。
题解:
显然满足题意的二进制串的首位必须是
0
。考虑一位一位地确定答案串。假设已经确定了答案串的前k 位,我们假设第
k+1
位是
0
,则要设法统计出满足条件的串的个数s 。那么如果
s<m
,则答案串第
k+1
位为
1
,同时m=m−s ;否则答案串第
k+1
位为
0
。于是问题转化为,统计所有长度为n 的,前缀为
prefix
的二进制串中,满足题目要求的串的个数。这是一类与数位有关的统计问题,于是很容易想到数位dp。那么考虑
dp[i][j][rev][inv]
表示,当前已经确定了前
i
位和末j 位,
rev
表示前
i
位与末j 位的逆序是否相等,
inv
表示前
i
位与末j 位的逆序取反后是否相等。状态转移比较显然,我们枚举第
i+1
位和第
n−i
位的取值,如果它满足
prefix
的限制,且新的串没有违反题目要求(可以利用
rev
和
inv
和取值判断), 那么更新
rev
和
inv
的状态,并累加到对应的新状态上。
注意:
如果
n
为奇数,那么dp到正中间一位的时候,这一位会同时作为前i 位和末
j
位的组成部分,需要特判。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, a[52];
ll k;
bool vis[55][55][2][2];
ll dp[55][55][2][2];
ll ans;
ll dfs(int l,int r,int rev,int inv)
{
if(l>r)return 1;
if(vis[l][r][rev][inv]==true)return dp[l][r][rev][inv];
vis[l][r][rev][inv] =true;
ll &ans=dp[l][r][rev][inv];
ans = 0;
for(int i=0;i<2;i++)
{
if(a[l] == -1 || a[l] == i)
{
for(int j = 0; j < 2; j++)
{
if(a[r] == -1 || a[r] == j)
{
if(l < r || i == j) //特判n为奇数的时候,走到最中间的一位
{
if(rev || i <= j)
{
if(inv || i <= 1 - j)
{
ans = ans + dfs(l + 1, r - 1, rev || i < j , inv || i < 1 - j);
}
}
}
}
}
}
}
return ans;
}
int main()
{
memset(a,-1,sizeof(a));
cin>>n>>k;
k++;
a[0]=0;
if(dfs(0,n-1,0,0)<k){
return 0*puts("-1");
}
for(int i=1;i<n;i++)
{
a[i]=0;
memset(vis,0,sizeof(vis));
ll cur=dfs(0,n-1,0,0);
if(cur<k)
{
k -= cur;
a[i]=1;
}
}
for(int i=0;i<n;i++)
{
cout<<a[i];
}
return 0;
}