洛谷 P4663

题面传送门

A:我该是有多无聊来写这种题的题解啊

B:大概是因为这题题解区里没有题解所以我来写一篇了,说明我有高尚的济世情怀(大雾

跑题了跑题了

首先看到字典序第 (i) 小小可以自然地想到按位决策,也就是说从高位到低位枚举,对于每一位我们强制令它为 I,看看有多少个符合要求的字符串,如果小于 (i) 就将这位改为 (1) 并继续往下枚举。

那么怎么求有多少个符合要求的字符串呢?这时候就要用到 DP 了。显然一个字符串可以成为某个字符串的正规读法当且仅当它的字典序 (le) 反串的字典序,因此我们很容易设计出一个 (dp) 状态:(dp_{i,j,o,x,y}) 表示当前决策了字符串的前 (i) 位和后 (i) 位,有 (j)XI 相邻,(o) 表示反串字典序是否等于原串字典序,从前往后数第 (i) 位为 (x),从后往前数第 (i) 位为 (y) 的方案数。转移就枚举第 (i+1) 位填的数 (u) 和第 (n-i) 位填的数 (v),如果 (o=1)(u>v) 就会导致原串字典序 (>) 反串字典序,不合法。否则显然有如下转移方程式子:

  • (dp_{i+1,j+[u e x]+[v e y],oland[u==v],u,v}leftarrow dp_{i,j,o,x,y})

注意特判 (i+1=n-i) 的情况和 (i+2=n-i) 的情况,对于前者而言必须有 (u=v),因为它俩是同一位,对于后者而言转移的第二维还需额外加上 ([u e v])

时间复杂度 (32n^3),跑得飞快(

const int MAXN=60;
const char str[3]="IX";
int n,k;ll m,dp[MAXN+5][MAXN+5][2][2][2];
int lim[MAXN+5];
ll calc(){
	memset(dp,0,sizeof(dp));
	for(int a=0;a<2;a++) for(int b=a;b<2;b++){
		if(~lim[1]&&(lim[1]^a)) continue;
		if(~lim[n]&&(lim[n]^b)) continue;
		dp[1][0][a==b][a][b]=1;
	} ll ans=0;
	for(int i=1;(i<<1|1)<=n;i++) for(int j=0;j<=k;j++)
		for(int o=0;o<2;o++) for(int x=0;x<2;x++) for(int y=0;y<2;y++){
			if(!dp[i][j][o][x][y]) continue;
			if((i<<1|1)==n){
				for(int u=0;u<2;u++){
					if(~lim[i+1]&&(lim[i+1]^u)) continue;
					if(j+(u^x)+(u^y)<=k) ans+=dp[i][j][o][x][y];
				}
			} else{
				for(int u=0;u<2;u++) for(int v=0;v<2;v++){
					if(~lim[i+1]&&(lim[i+1]^u)) continue;
					if(~lim[n-i]&&(lim[n-i]^v)) continue;
					if(o&&u>v) continue;
					if((i+1<<1)==n){
						if(j+(u^x)+(u^v)+(v^y)<=k) ans+=dp[i][j][o][x][y];
					} else dp[i+1][j+(u^x)+(v^y)][o&&u==v][u][v]+=dp[i][j][o][x][y];
				}
			}
		}
	return ans;
}
int main(){
	scanf("%d%d%lld",&n,&k,&m);
	if(n==1) return ((m>2)?puts("NO SUCH STONE"):(putchar(str[m-1]))),0;
	if(n==2){
		if(k==0) return ((m>2)?puts("NO SUCH STONE"):(putchar(str[m-1]),putchar(str[m-1]))),0;
		else return ((m>3)?puts("NO SUCH STONE"):((m&1)?(putchar(str[m>>1]),putchar(str[m>>1])):puts("IX"))),0;
	} memset(lim,-1,sizeof(lim));
	if(m>calc()) return puts("NO SUCH STONE")&0;
	for(int i=1;i<=n;i++){
		lim[i]=0;ll sum=calc();
		if(m>sum) m-=sum,lim[i]=1;
	}
	for(int i=1;i<=n;i++) putchar(str[lim[i]]);
	return 0;
}
原文地址:https://www.cnblogs.com/ET2006/p/luogu-P4663.html