考虑二分答案,转化为求有多少$\leq lim$的数满足条件。
从两侧往中间进行数位DP,设$f[l][r][j][x][y][z][pre][suf]$表示当前准备填的两个位置是$l$和$r$,已经有$j$对相邻的数不同,$l-1$填的是$x$,$r+1$填的是$y$,正序串和逆序串的大小关系为$z$,正序串和$lim$的大小关系为$pre$,逆序串和$lim$的大小关系为$suf$的方案数。
注意到$r=n-l+1$,因此可以省去$r$这一维,然后枚举接下来填什么数进行转移即可。
时间复杂度$O(2^6n^2k)$。
#include<cstdio> typedef long long ll; const ll inf=1LL<<61; const int N=65; int n,m,i,j,S,l,r,x,y,z,p,s,nj,nz,np,ns,A,B,a[N];ll L,R,mid,K,ans=-1,f[N][N][1<<5]; inline void up(ll&a,ll b){a+=b;if(a>inf)a=inf;} inline ll check(ll mid){ for(i=n;i;i--)a[i]=mid&1,mid>>=1; for(i=1;i<=n;i++)for(j=0;j<=m;j++)for(S=0;S<32;S++)f[i][j][S]=0; ll ret=0; for(f[0][0][0]=l=1,r=n;l<=r;l++,r--)for(j=0;j<=m;j++)for(S=0;S<32;S++)if(f[l-1][j][S]){ x=S&1,y=S>>1&1,z=S>>2&1,p=S>>3&1,s=S>>4&1; if(l<r)for(A=0;A<2;A++)for(B=0;B<2;B++){ nj=j; if(l>1)nj+=(A^x)+(B^y); if(nj>m)continue; if(z)nz=1; else{ if(A>B)continue; nz=A<B; } if(p)np=1; else{ if(A>a[l])continue; np=A<a[l]; } if(B==a[r])ns=s;else ns=B>a[r]; up(f[l][nj][A|(B<<1)|(nz<<2)|(np<<3)|(ns<<4)],f[l-1][j][S]); } if(l==r)for(A=0;A<2;A++){ if(l>1&&j+(A^x)+(A^y)>m)continue; if(!p&&(A>a[l]||A==a[l]&&s))continue; up(ret,f[l-1][j][S]); } } if(n%2==0)for(j=0;j<=m;j++)for(S=0;S<32;S++)if(f[l-1][j][S]){ x=S&1,y=S>>1&1; if(j+(x^y)>m)continue; p=S>>3&1,s=S>>4&1; if(!p&&s)continue; up(ret,f[l-1][j][S]); } return ret; } int main(){ scanf("%d%d%lld",&n,&m,&K); L=0;R=(1LL<<n)-1; while(L<=R)if(check(mid=(L+R)>>1)>=K)R=(ans=mid)-1;else L=mid+1; if(ans<0)puts("NO SUCH STONE");else for(i=n-1;~i;i--)putchar(ans>>i&1?‘X‘:‘I‘); return 0; }
时间: 2024-10-10 09:10:56