Question 问题 P10161 [DTCPC 2024] 小方的疑惑 10
要求构造一个长度为 n n n 的括号字符串,其中 k k k 个子串为合法的括号序列。无法构造输出 − 1 -1 −1。
Analysis 分析
手玩一下可以发现最优情况有以下两种情况:
()()...()
如果有 x x x 对括号,则有 x ( x + 1 ) 2 \frac{x(x+1)}{2} 2x(x+1) 个字串为合法的括号序列。(()()...())
相当于在 1 1 1 情况外边加一层,多出一个合法字串,并且让当前括号对数变为 1 1 1 对。
以下令 s ( x ) = x ( x + 1 ) 2 s(x)=\frac{x(x+1)}{2} s(x)=2x(x+1)。
Solution 构造
我们可以通过这样构造类似 ( ( ()()() )()() )()()() \texttt{\color{red}{(\color{blue}(\color{black}()()()\color{blue})()()\color{red})()()()}} ((()()())()())()()() 这样的括号序列,用颜色标记出来了。计算的话分三层分别是最里面的 3 3 3 对,中间的 2 + 1 2+1 2+1 对,最外层 3 + 1 3+1 3+1 对( + 1 +1 +1 的原因是第 2 2 2 种情况会使括号对变为 1 1 1,要加上)。最终总共的合法字串数为: s ( 3 ) + s ( 3 ) + s ( 4 ) = 22 s(3)+s(3)+s(4)=22 s(3)+s(3)+s(4)=22 对。
也就是说,我们相当于把这个构造的字符串分为 m m m 层,每层填 a i a_i ai 对括号,则总的合法括号字串数为 ∑ i = 1 m s ( a i ) \displaystyle \sum_{i=1}^{m} s(a_i) i=1∑ms(ai) 。
其实这个问题相当于完全背包。
背包容量为 k k k,每个货物(也就是每一层填入的括号组数)的体积是 s ( x ) s(x) s(x) ,价值是 x x x,每一件可以无限选。问填满背包的最小价值。
跑一遍完全背包,记录一下转移的路径,输出即可。无解即为最小值比 n n n 大。
Specific 细节
-
如果有剩余的部分全部输出
'('
。 -
除第一层外的每一层都有多一对括号对,输出时要注意
-
输出答案我使用了一个抽象方法,详见代码。
-
贪心找最大的 s ( x ) s(x) s(x) 去填可以被卡,数据如下(但好像很少可以被卡掉)。
input
28 54
output
((()()()()()()()()())()())()
Code 代码
int T,n,m;
int v[N],w[N],f[N],turn[N];
signed main(){
read(T);
for(rint i=1;i<=N-8;i++) w[i]=-(2*i),v[i]=(i+1)*i/2;//预处理货物的体积和价值
for(rint i=1;i<=N-8;i++) f[i]=-inf;
for(rint i=1;i<=N-8;i++){
for(rint j=v[i];j<=N-8;j++){
if(f[j]<f[j-v[i]]+w[i]) turn[j]=i/*记录转移路径*/,f[j]=f[j-v[i]]+w[i];
}
}//完全背包
while(T--){
read(n,m);
if(-f[m]>n) puts("-1");
else{
//细节3:神奇输出方式
//构造一个长度为两倍的字符串,记录左端点 l 和右端点 r,两个都在中间
//这样就可以一边填括号,一边在外边套一层括号
//最后输出 [l,r] 里的字符串
char s[200008]="";int l=100000,r=100000,now=m;
for(rint i=turn[now];i;i=turn[now]){//沿着记录的路径倒推
for(rint j=1;j<=i-(now!=m)/*细节2*/;j++) s[r]='(',s[r+1]=')',r+=2;//往右填括号对 例:()()
l--;s[l]='(';s[r]=')';r++;//外面包一层 例:(()())
now=now-v[turn[now]];//沿着记录的路径倒推
}
l++;r-=2;//会多包一层,删掉
for(rint i=l;i<=r;i++) putchar(s[i]);
for(rint i=r-l+1;i<n;i++) putchar('(');puts("");//细节1:补充剩余的左括号 例:(()())((((
}
}
return 0;
}
Proof 补充证明
为何如上的构造是消耗最小括号的构造方案?其实任意一种括号序列都可以转化为其上的方案。任意一种括号序列可以看作合法括号序列 a i a_i ai 和非法括号序列 b i b_i bi 相交织,也就是如下形式:
b 1 a 1 b 2 a 2 . . . a m − 1 b m b_1a_1b_2a_2...a_{m-1}b_m b1a1b2a2...am−1bm
Step 1 扔掉最两边的 b 1 b_1 b1 和 b m b_m bm,不影响结果。转化后为: a 1 b 2 a 2 . . . a m − 1 a_1b_2a_2...a_{m-1} a1b2a2...am−1
Step 2 我们发现每两个合法括号序列之间必有一非法括号序列相隔,我们可以把 a 1 a_1 a1 塞入 a 2 a_2 a2 的第一个括号里,然后把 b 2 b_2 b2 扔掉,合法字串数显然不变而消耗的括号减少了 b 2 b_2 b2 个。以此类推,把 a 2 a_2 a2 塞入 a 3 a_3 a3,扔掉 b 3 b_3 b3 一直到 a m − 2 a_{m-2} am−2 塞入 a m − 1 a_{m-1} am−1 扔掉 b m − 1 b_{m-1} bm−1。最后出来的即为我们以上构造的方案。而长度较原先减少了 ∑ i = 1 m b i \sum_{i=1}^m b_i ∑i=1mbi 个括号,故为最短的构造方案。
模拟一下
原括号序列 )))(()()()(((()())(()()((( \texttt{)))(()()()(((()())(()()(((} )))(()()()(((()())(()()(((
Step 1 )))( ()()() (( (()())() ( ()() ((( \texttt{)))( \color{#00BA00}()()() \color{black}(( \color{blue}(()())() \color{black}( \color{red}()() \color{black}(((} )))( ()()() (( (()())() ( ()() (((
()()() (( (()())() ( ()() ~~~~~~~~~~\texttt{\color{#00BA00}()()() \color{black}(( \color{blue}(()())() \color{black}( \color{red}()()} ()()() (( (()())() ( ()()
Step 2 (( ()()() )())() ( ()() \texttt{\color{blue}((\color{#00BA00}()()()\color{blue})())() \color{black}( \color{red}()()} ((()()())())() ( ()()
( (( ()()() )())() )() ~~~~~~~~~~\texttt{\color{red}(\color{blue}((\color{#00BA00}()()()\color{blue})())()\color{red})()} (((()()())())())()
题外话
string 好像也没事?