D-二分

lyrrr發表於2024-08-27

最近做了一個CFround166的d題然後發現我並不會二分(雖然標答並不是二分)。故來寫一下。

題目:https://codeforces.com/contest/1976/problem/D

首先觀察到幾個顯而易見的性質:

1.若要翻轉[l,r],[l,r]中的(和)數量相等

2.為了能和前面匹配上,翻轉後[l,r]中未匹配右括號的(最大)數量要等於[1,l-1]中未匹配左括號的數量。

好的到這裡我就不會了。但是如果考慮左括號比右括號多的數量並且把每個位置的值設為{a_i},那麼就可以實現這兩個性質的量化(這一步真的想不到,感覺數學思維要加強,其實有點像一個入棧出棧的操作吧。

1.\({a_{l-1}==a_r}\)

2.\({a_{l-1}>=a_i-a{l-1}}\)(最大未匹配的右括號不能多於未匹配的左括號),可變形簡化為\(a_{l-1}*2>=a_i\)

那麼我們想如果固定l-1,r的取值一定是l-1右邊連續的一個範圍,因為有一個不滿足的顯然右邊所有點都不滿足。

我:這個不能二分,因為二分要在有序的序列上進行。 佬:答案具有單調性就可以二分。

然後我想了一下用線段樹維護mx的話確實是可以二分的。所以寫完了。

#include<bits/stdc++.h>
using namespace std;
#define ls(x) (x<<1)
#define rs(x) ((x<<1)+1)
#define mid ((l+r)>>1)
#define int long long
const int mod=1e9+7;
const int maxn=2e5+10;

int sta[maxn],pos[maxn]; 
int t[maxn*4];
vector<int>E[maxn];

void build(int x,int l,int r){
	if(l==r){
		t[x]=sta[l];return;
	}
	build(ls(x),l,mid);
	build(rs(x),mid+1,r);
	t[x]=max(t[ls(x)],t[rs(x)]);
}
int	que(int x,int ql,int qr,int l,int r){
	if(l>=ql&&r<=qr){
		return t[x];
	}
	int res=0;
	if(mid>=ql)res=que(ls(x),ql,qr,l,mid);
	if(mid<qr)res=max(res,que(rs(x),ql,qr,mid+1,r));
	return res;
}

void solve(){
	string s;cin>>s;
	for(int i=0;i<=s.size();i++)E[i].clear(),pos[i]=0;
	for(int i=0,w=0;i<s.size();i++){
		if(s[i]=='(')w++;
		else w--;
		sta[i+1]=w;
		E[w].push_back(i+1);
		pos[i+1]=E[w].size()-1;
	}
	int n=s.size(),ans=0;
	build(1,1,n);
	//cout<<1;
	for(int i=1;i<s.size();i++){
		int l=i+1,r=n,mid1=(l+r+1)>>1;
		while(l<r){
			if(que(1,l,mid1,1,n)<=sta[i]*2){
				l=mid1;
			}
			else r=mid1-1;
			mid1=(l+r+1)>>1;
		}
		int w=sta[i];
		l=0,r=E[w].size()-1;
		int mid2=(l+r+1)>>1;
		while(l<r){
			if(E[w][mid2]<=mid1)l=mid2;
			else r=mid2-1;
			mid2=(l+r+1)>>1;
		}
		ans+=mid2-pos[i];
	}	
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	int tt;cin>>tt;while(tt--){
		solve();
	}	
	
}