P10161 [DTCPC 2024] 小方的疑惑 10

Question 问题 P10161 [DTCPC 2024] 小方的疑惑 10

要求构造一个长度为 \(n\) 的括号字符串,其中 \(k\) 个子串为合法的括号序列。无法构造输出 \(-1\)

Analysis 分析

手玩一下可以发现最优情况有以下两种情况:

  1. ()()...() 如果有 \(x\) 对括号,则有 \(\frac{x(x+1)}{2}\) 个字串为合法的括号序列。
  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 细节

  1. 如果有剩余的部分全部输出 '('
  2. 除第一层外的每一层都有多一对括号对,输出时要注意
  3. 输出答案我使用了一个抽象方法,详见代码。
  4. 贪心找最大的 \(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\)),也就是如下形式:

\[b_1a_1b_2a_2...a_{m-1}b_m \]

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 输出好像也没事?

posted @ 2024-05-29 21:51  Mr_Azz  阅读(0)  评论(0编辑  收藏  举报