【xsy1281】 珠串 打表+乱搞or数位dp
题目大意:你要找出一个有$k$个的本质不同的$n$位二进制数的集合,使得集合中最大的数最小,请输出这个数
本质不同定义:对于一个数$k$,$rev(k)$,$~k$,$rev(~k)$与$k$本质相同。其中$~k$表示对$k$的每一位二进制翻转,$rev(k)$表示对$k$左右翻转。
举个例子:对于数0001,它与1000,1110,0111本质相同。
数据范围:$n≤25,k≤10^{16}$。
此题貌似正解是数位dp,然而我比较菜。
看到这一题:打表啊!
于是打了个表,发现:
若$k≤2^{\lceil \frac{n}{2} \rceil}-2$,则直接输出$k$就可以了,证明显然。
若$k>2^{\lceil \frac{n}{2} \rceil}-2$
先考虑$n$为偶数的情况,我们打一个表,打出所有满足$rev(x)<x$或$(~x)<x$或$rev(~x)<x$的数,大概长这样
我们发现:以中间的分界线为界,当左侧构成的数去掉前导零后构成$x$,那么以$x$开头的不符合要求的二进制数就有$2x$个。
(感兴趣的同学可以证明一下,我懒得证了23333)
我们基于这一个特征,先确定这个二进制数的前$\frac{n}{2}$位。
后面的$\frac{n}{2}$位直接暴力枚举然后再随便判断一下就好了。
n为奇数的情况相似
以中间为分界线,当右侧横线左侧的数为$x$时,以$x$为前缀的$n$位二进制数有$x$个。
和之前的搞法一样随便搞一搞就可以了。
时间复杂度:$O(2^{n/2})$,空间复杂度:$O(2^{n/2})$。
1 #include<bits/stdc++.h> 2 #define L long long 3 using namespace std; 4 5 int rev[1<<25]={0}; 6 L n,k; 7 8 void out(L k1){for(L i=0;i<n;i++) printf("%d",bool((1LL<<(n-i-1))&k1));} 9 10 void SolveEven(){ 11 L n2=n/2,s=1<<n2,all=1LL<<n; 12 if(k<s){ 13 out(k); 14 return; 15 }else k-=s; 16 for(int i=1;i<s;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?(s>>1):0); 17 for(int i=1,j=2;i<s;i++,j+=2){ 18 if(k>=s-j) k-=s-j; 19 else{ 20 int p; for(p=0;p<s&&k>=0;p++){ 21 L k1=(1LL*i)<<n2|p; 22 L k2=(1LL*rev[p])<<n2|rev[i]; 23 L k3=(1LL*rev[(s-1-p)&(s-1)])<<n2|rev[s-1-i]; 24 if(k2>=k1&&k3>=k1) 25 k--; 26 } 27 L k1=(1LL*i)<<n2|(p-1); 28 out(k1); 29 return; 30 } 31 } 32 cout<<-1<<endl; 33 } 34 void SolveOdd(){ 35 L n2=n/2,s=1<<n2,all=1LL<<n; 36 L N2=(n+1)/2,S=1<<N2; 37 if(k<S-1){ 38 out(k); 39 return; 40 }else k-=S-1; 41 for(int i=1;i<s;i++) rev[i]=(rev[i>>1]>>1)|((i&1)?(s>>1):0); 42 for(int i=2,j=2;i<s;i++,j++){ 43 if(k>=s-j) k-=s-j; 44 else{ 45 int p; for(p=0;p<s&&k>=0;p++){ 46 L k1=(1LL*i)<<n2|p; 47 L k2=((i&1)<<n2)|((1LL*rev[p])<<N2)|rev[i>>1]; 48 L k3=(((i&1)==0)<<n2)|((1LL*rev[(s-p-1)&(s-1)])<<N2)|rev[s-1-(i>>1)]; 49 if(k2>=k1&&k3>=k1) 50 k--; 51 } 52 L k1=(1LL*i)<<n2|(p-1); 53 out(k1); 54 return; 55 } 56 } 57 cout<<-1<<endl; 58 } 59 60 int main(){ 61 cin>>n>>k; 62 if(n&1) SolveOdd(); 63 else SolveEven(); 64 }