P10161 [DTCPC 2024] 小方的疑惑 10

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

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

Analysis 分析

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

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

其实这个问题相当于完全背包

背包容量为 k k k,每个货物(也就是每一层填入的括号组数)的体积是 s ( x ) s(x) s(x) ,价值是 x x x,每一件可以无限选。问填满背包的最小价值。

跑一遍完全背包,记录一下转移的路径,输出即可。无解即为最小值比 n n n 大。

Specific 细节

  1. 如果有剩余的部分全部输出 '('

  2. 除第一层外的每一层都有多一对括号对,输出时要注意

  3. 输出答案我使用了一个抽象方法,详见代码。

  4. 贪心找最大的 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...am1bm

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...am1

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} am2 塞入 a m − 1 a_{m-1} am1 扔掉 b m − 1 b_{m-1} bm1。最后出来的即为我们以上构造的方案。而长度较原先减少了 ∑ 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 好像也没事?

  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值