最近做了一個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();
}
}