P10161 [DTCPC 2024] 小方的疑惑 10
Question 问题 P10161 [DTCPC 2024] 小方的疑惑 10
要求构造一个长度为 \(n\) 的括号字符串,其中 \(k\) 个子串为合法的括号序列。无法构造输出 \(-1\)。
Analysis 分析
手玩一下可以发现最优情况有以下两种情况:
()()...()
如果有 \(x\) 对括号,则有 \(\frac{x(x+1)}{2}\) 个字串为合法的括号序列。(()()...())
相当于在 \(1\) 情况外边加一层,多出一个合法字串,并且让当前括号对数变为 \(1\) 对。
以下令 \(s(x)=\frac{x(x+1)}{2}\)。
Solution 构造
我们可以通过这样构造类似 \(\texttt{\color{red}{(\color{blue}(\color{black}()()()\color{blue})()()\color{red})()()()}}\) 这样的括号序列,用颜色标记出来了。计算的话分三层分别是最里面的 \(3\) 对,中间的 \(2+1\) 对,最外层 \(3+1\) 对(\(+1\) 的原因是第 \(2\) 种情况会使括号对变为 \(1\),要加上)。最终总共的合法字串数为: \(s(3)+s(3)+s(4)=22\) 对。
也就是说,我们相当于把这个构造的字符串分为 \(m\) 层,每层填 \(a_i\) 对括号,则总的合法括号字串数为 \(\displaystyle \sum_{i=1}^{m} s(a_i)\) 。
其实这个问题相当于完全背包。
背包容量为 \(k\),每个货物(也就是每一层填入的括号组数)的体积是 \(s(x)\) ,价值是 \(x\),每一件可以无限选。问填满背包的最小价值。
跑一遍完全背包,记录一下转移的路径,输出即可。无解即为最小值比 \(n\) 大。
Specific 细节
- 如果有剩余的部分全部输出
'('
。 - 除第一层外的每一层都有多一对括号对,输出时要注意
- 输出答案我使用了一个抽象方法,详见代码。
- 贪心找最大的 \(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\) 和非法括号序列 \(b_i\) 相交织(\(a_i\) 和 \(b_i\) 可以是 \(0\)),也就是如下形式:
Step 1 扔掉最两边的 \(b_1\) 和 \(b_m\),不影响结果。转化后为:\(a_1b_2a_2...a_{m-1}\)
Step 2 我们发现每两个合法括号序列之间必有一非法括号序列相隔,我们可以把 \(a_1\) 塞入 \(a_2\) 的第一个括号里,然后把 \(b_2\) 扔掉,合法字串数显然不变而消耗的括号减少了 \(b_2\) 个。以此类推,把 \(a_2\) 塞入 \(a_3\),扔掉 \(b_3\) 一直到 \(a_{m-2}\) 塞入 \(a_{m-1}\) 扔掉 \(b_{m-1}\)。最后出来的即为我们以上构造的方案。而长度较原先减少了 \(\sum_{i=1}^m b_i\) 个括号,故为最短的构造方案。
模拟一下
原括号序列 \(\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
输出好像也没事?