题解:P10161 [DTCPC 2024] 小方的疑惑 10
lcfollower · · 题解
好题,我不会,竟然是背包思想的 DP。
:::info[一个括号序列里的合法括号序列个数怎么算?]{open}
如果有
容易发现答案为
再考虑如果有括号嵌套,先来一种简单的:
然后还有
最后我们将上述的情况混合,比如
:::
这样就很明确了,我们设
我们发现这是一个背包模型,因此我们可以用背包的转移方式。
设我们需要预处理
然后对于询问
否则考虑怎么输出一个合法的括号序列,我们用
在输出方案数的时候,我们用递归的形式输出,我们可以在第一个括号里面摆上剩下的括号,然后第
最后如果发现有多余长度没有摆上任何括号,剩下的我们直接输出 ( 即可,这样构不成任何合法的括号序列。
分析到这应该明白为啥要最少了,这里留给读者自己思考。
:::info[代码]
inline void print (int k){
if (!k) return;//没有了就别输出了。
putchar ('(');//第一个 () 的左括号,要在后面输出一点东西。
print (k - a[lst[k]]);//剩下的括号递归输出。
putchar (')');//第一个 () 的右括号,包裹起来。
up (i ,2 ,lst[k]) putchar ('(') ,putchar (')');//余下的直接输出 ()()()...() 即可。
} inline void solve (){
n = read () ,k = read ();
if (f[k] > n) {puts ("-1") ; return ;}//无解。
print (k);
up (i ,f[k] + 1 ,n) putchar ('(');//存在剩余括号,全输出 ( 即可。
puts ("");
} signed main() {
up (i ,1 ,1e5) {
if (i * (i + 1) / 2 <= 1e5) a[++ cnt] = i * (i + 1) / 2;//预处理所有可能的长度。
else break;
}
memset (f ,0x3f ,sizeof f);
f[0] = 0;//初始化。
up (i ,1 ,cnt)//进行背包模型的转移。
up (j ,a[i] ,1e5)
if (f[j] > f[j - a[i]] + 2 * i) {
f[j] = f[j - a[i]] + 2 * i ,lst[j] = i;//顺便记录 lst。
}
int T = read ();
while (T --) solve ();
return 0;
}
:::