[CF1685C]Bring Balance
做题时间:2022.8.11
\(【题目描述】\)
给定一个长为 \(2n(1\leq \sum n\leq 2\times 10^5)\) 的括号序列 \(s\) ,包含 \(n\) 个左括号和 \(n\) 个右括号,现在可以反转 \(s\) 的一些字串,问最少反转多少次可以将 \(s\) 变成合法字串,并输出构造方案。
\(【输入格式】\)
多组数据,第一行一个整数 \(T\) 表示数据组数
每组数据第一行一个 \(n\)
第二行一个长为 \(2n\) 的括号序列 \(s\)
\(【输出格式】\)
对于每组数据,第一行一个整数表示最少操作次数 \(k\)
接下来 \(k\) 行每行两个整数 \(l_i,r_i\) 表示反转的区间
\(【考点】\)
数形结合
\(【做法】\)
分析括号序列、01序列等可以考虑数形结合,即将左括号(1)看成+1,右括号(0)看成-1,建立一个折线图再分析。
对于这道题,经过观察发现,反转一个区间 \([l,r]\) 相当于将其中的点以线段 \(lr\) 的中点进行中心对称,而目标即为将所有低于x轴的点(下称为关键点)全部翻上x轴,但这也会使得原先高于x轴的点翻下x轴。而 \([l,r]\) 中高度最高的肯定最容易被翻下x轴,根据几何证明可以发现,假设 \(a\) 为最高点,若 \(h_a\leq h_l+h_r\) 则反转 \([l,r]\) 时不会将任何一个点翻下x轴,因此可以考虑找可以将所有关键点包括在其中并符合上述条件的 \([l,r]\) ,然后直接反转 \([l,r]\) 即可;若找不到,记 \(\max\) 为高度最高的点,则反转 \([l,\max]\) 和 \([\max,n\times 2]\) 即可。
#include<cstdio>
#include<iomanip>
#define INF 0x7f7f7f7f
using namespace std;
const int N=2e5+50;
char s[N];
int h[N],pm,pk[N],ed1,ed2;
//pm记录最高点,pk记录所有低于x轴的点
int n,t;
inline int Max(int a,int b){return a>b?a:b;}
pair<int,int> Work(int l,int r)//计算[l,r]的最大值
{
int maxn=-INF,pos=0;
for(int i=l;i<=r;i++){
if(h[i]>maxn){
maxn=h[i],pos=i;
}
}
return make_pair(maxn,pos);
}
bool Check()
{
int ml=0,mr=0,mm=0;
pair<int,int> l,r,mid;
//注意这里h[i]表示i的前缀和,因此0才表示第一个括号,下面输出左区间+1也是同理
l=Work(0,pk[1]);
mid=Work(pk[1],pk[ed2]);
r=Work(pk[ed2],n*2);
if(l.first+r.first>=mid.first){//有[l,r]满足条件
printf("1\n");
printf("%d %d\n",l.second+1,r.second);
return true;
}
return false;
}
void Print()
{
if(!ed2){//原序列为合法序列,不需要反转
printf("0\n");
return ;
}
if(!Check()){//若没有[l,r]满足条件
printf("2\n");
printf("1 %d\n%d %d\n",pm,pm+1,2*n);
}
}
void Pre()//初始化点的高度
{
ed1=ed2=0;
for(int i=1;i<=n*2;i++){
if(s[i]=='(') h[i]=h[i-1]+1;
else h[i]=h[i-1]-1;
}
}
void Solve()
{
scanf("%d",&n);
scanf("%s",s+1);
Pre();
int maxn=-INF;//注意有的点可能<0,因此maxn不能设为0,上同
for(int i=1;i<=n*2;i++){
if(h[i]>=maxn){
pm=i;
maxn=h[i];
}
if(h[i]<0) pk[++ed2]=i;
}
}
int main()
{
scanf("%d",&t);
while(t--){
Solve();
Print();
}
return 0;
}

浙公网安备 33010602011771号