1 条题解
-
0
题解
我们要按字典序枚举所有长度为 、相邻异字符次数不超过 的串,并认为一个串与其反转后相同(180° 旋转)是同一块石头,取两者中字典序较小的一种作为正规表示。为了直接在正规表示序列上做排名,代码使用“逐位构造 + 动态规划”:
p[i]
表示第 位当前被强制为I/0
或X/1
,初始全为-1
。主程序按字典序从前往后尝试把第 位设为I
,调用cal()
统计这种前缀下的合法补全个数;若不足以覆盖目标排名,就改设为X
并相应地扣掉数量。cal()
把串从两端往中间成对地扩展。状态dp[pos][cnt][L][R][flag]
含义为:已经确定了前pos
对字符(如果 为奇数,中间字符单独处理),累计有 次相邻异字符,最外层字符分别为 、,flag
表示之前是否已经确定“左边严格小于右边”(保证我们只计入正规表示)。每次选择新的一对字符 时:- 如果这一对是中心处且 ,则需要额外计一次变化(因为串与反串不同)。
- 在没有证明左串严格小于右串之前(
flag=0
),必须保持 ,否则翻转后的串会更小。 - 邻位之间是否不同通过 累加到 中。
- 若 的终态都会累积到答案里;如果最外层字符已经固定,还要在 DP 开始前检查它们是否满足 (否则正规表示应该是翻转后的那一侧)。
整套 DP 只依赖于 的一半,因此复杂度约为 ,配合外层的逐位扫描即可在 的范围内完成排名查询;若最终计数仍小于所需排名,就说明不存在对应的石头,输出
NO SUCH STONE
。//test #include<iostream> #include<cstring> #define mem(a,b) memset(a, b, sizeof(a)) #define rep(i,a,b) for(int i = (a); i <= (b); i++) #define per(i,b,a) for(int i = (b); i >= (a); i--) #define N 66 #define ll long long using namespace std; ll dp[N][N][2][2][2], rnk; int p[N], n, k; ll cal(){ mem(dp, 0); if(~p[n]){ if(p[n] < p[1]) return 0; dp[1][0][p[1]][p[n]][p[n]>p[1]] = 1; } else rep(i,p[1],1) dp[1][0][p[1]][i][i > p[1]] = 1; rep(i,1,(n+1)/2-1) rep(j,0,n-1){ int sa = (~p[i+1] ? p[i+1] : 0), sb = (~p[n-i] ? p[n-i] : 0); rep(a,sa, (~p[i+1]?sa:1)) rep(b,sb, (~p[n-i]?sb:1)){ if((n&1) && i == (n+1)/2-1 && a != b) continue; int ex = ((i == (n+1)/2-1) && (a^b)); rep(c,0,1) rep(d,0,1) rep(e,0,1) if(dp[i][j][c][d][e]) if(b >= a || e) dp[i+1][j+(a^c)+(b^d)+ex][a][b][e|(b>a)] += dp[i][j][c][d][e]; } } ll ans = 0; rep(a,0,1) rep(b,0,1) rep(c,0,1) rep(j,0,k) ans += dp[(n+1)/2][j][a][b][c]; return ans; } int main(){ cin>>n>>k>>rnk; mem(p, -1); rep(i,1,n){ p[i] = 0; ll I = cal(); if(rnk > I){ rnk -= I, p[i] = 1; ll X = cal(); if(X < rnk) return puts("NO SUCH STONE"), 0; } } rep(i,1,n) cout<< (p[i] ? 'X' : 'I'); cout<<endl; return 0; }
- 1
信息
- ID
- 3244
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者