這一題好妙啊
首先把括號序列轉化成平面直角坐標系 $xOy$ 上的折線,初始時折線從坐標系原點 $(0,0)$ 出發
如果第 $i$ 個位置是 '(' 那么折線就往上走一步($y+1$),否則往下走一步 ($y-1$)
這條折線有很多有用的性質
$1.$如果某個時刻折線的縱坐標為負數了,那么說明這個括號序列一定是不合法的
證明也挺好理解的,變成負數說明沒有足夠的 '(' 和 ')' 匹配了,顯然不合法
$2.$如果最終位置 $n$ 的折線 $y$ 坐標不為 $0$ ,那么一定不合法,因為這樣說明 '(' 和 ')' 數量不相等
$3.$如果以上兩條都不滿足,那么括號序列合法,證明應該是挺顯然的吧,即任何位置的 ')' 都能得到匹配並且最后全部匹配完畢
並且可以發現,在保證最終位置折線 $y$ 坐標為 $0$ 的情況下,這個括號序列的不同循環移位的合法序列數即為 這些到達最小值的位置數
設這些位置的序列為 $p$
對初始序列進行循環移位 $p_1,p_2,p_3...$ 后括號序列是合法的,並且其他的循環移位后都不合法
證明首先設 $a_1,a_2,a_3...a_n$ 是初始時的括號序列
那么對於循環移位 $p$ ,相當於把序列 $a$ 變成 $a_{p+1}a_{p+2}...a_{n}a_{1}a_{2}a_{3}...a_{p}$
那么假設原本折線在位置 $p$ 時的 $y$ 的值為 $t$ (顯然 $t<=0$)
那么對於 $(p,n]$ 的這一段折線,顯然它的形狀不會變,並且 $y$ 坐標同時增加 $-t$
這個挺好理解的吧,首先 $p$ 位置的 $y$ 變成 $0$ ,后面的就和 $p$ 位置一起變了
然后 $[0,p]$ 這一段折線再接到原本位置 $n$ 的后面(顯然這一段折線也增加了 $-t$),看圖理解吧:
上圖展示了循環移位 $p1$ 后折線的變化
由於 $t$ 是初始折線最小的縱坐標,那么循環移位后就不存在任何位置的 $y$ 小於 $0$
由上圖你會發現所有的 $y$ 都增加了 $-t$,又因為折線的形狀不變所以所有位置的 $y$ 都大於等於 $0$
然后又因為最終位置的 $y$ 一定為 $0$(由於初始時位置 $n$ 的 $y$ 為 $0$,那么左右括號數量永遠相等,不管怎么循環移位最終位置都是 $y=0$)
那么說明這一段循環移位后括號序列一定是合法的(由之前證明的第 $3$ 條可得)
現在只剩下證明其他的循環移位都不合法即可,其實十分顯然,因為從其他位置循環移位后坐標增加量不到 $-t$
那么對於初始為 $t$ 的位置增加后還是小於 $0$,還是不合法
所以我們經歷了千辛萬苦終於證明了官方題解里一句話的東西:
It can easily be shown that the answer to the question of the number of cyclic shifts being correct bracket sequence is the number of times how much minimum balance is achieved in the array of prefix balances.
如果此時你理解了循環移位對於折線的變化,那么就很容易知道交換兩個位置對折線的變化
如果交換 ')' 和 '(' ,會導致一段區間的 $y$ 加 $2$ ,那么對答案毫無意義,只會讓答案變小
所以只能交換 '(' 和 ')' ,那么會導致區間的 $y$ 減 $2$,如果你改變的區間跨過了某個 $p_i$ ($p_i$ 的定義同上)
那么只會讓答案更小,因為此時只有跨過的 $p$ 會產生貢獻,還不如原本更多的 $p$
為了方便,現在讓我們把折線用循環移位變成任意時刻都大於等於 $0$ 的情況(顯然對任意一個 $p_i$ 進行循環移位 $p_i$ 即可)
此時所有初始位置為 $p$ 的位置的 $y$ 均為 $0$,設此時 $y$ 為 $0$ 的位置為新的序列 $p$
因為之前證明的,交換只能在區間 $(p_i,p_{i+1})$ 進行,如果我們把 $p_{i}+1$ (顯然是 '(')和 $p_{i+1}$ 交換(顯然是 ')')
那么區間內所有 $y$ 值為 $1$ 的位置因為減 $2$ 就變成了最小的位置(區間內不存在 $y=0$ 的位置),那么交換以后序列的答案即為初始這一段區間內 $1$ 的個數
為了答案最大我們顯然只要考慮 $(p_i,p_{i+1})$ 區間內 $1$ 的個數
然后考慮把 $y=2$ 的位置通過減 $2$ 變成 $0$ ,那么答案就是初始時的答案加上區間內 $y=2$ 的貢獻(注意此時區間內不能有 $0,1$)
我們同樣可以找到這些位置,然后操作一下看看答案是多少即可,實現起來還是有點細節的
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<vector> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=3e5+7; int n,cnt,sl,sr,sum[N]; char s[N],tmp[N]; int main() { n=read(); scanf("%s",s+1); int pos=0,now=0,mi=0; for(int i=1;i<=n;i++) { now+=s[i]=='(' ? 1 : -1; if(now<mi) mi=now,pos=i; } if(now!=0) { printf("0\n1 1\n"); return 0; } for(int i=1;i<=n;i++) tmp[i]=s[ (pos+i-1)%n+1 ]; vector <int> V; V.push_back(0); for(int i=1;i<=n;i++) { s[i]=tmp[i]; sum[i]=sum[i-1] + (s[i]=='(' ? 1 : -1); if(!sum[i]) cnt++,V.push_back(i); } int ans=cnt,L=1,R=1,len=V.size(); for(int i=1;i<len;i++) { int l=V[i-1]+1,r=V[i],t=0; for(int j=l;j<=r;j++) t+=sum[j]==1; if(t>ans) ans=t,L=l,R=r; } V.clear(); for(int i=1;i<=n;i++) if(sum[i]==1) V.push_back(i); len=V.size(); for(int i=1;i<len;i++) { int l=V[i-1]+1,r=V[i],t=cnt; if(sum[l]==0) continue;//如果sum[l]不為0,那么sum[l]一定為2,那么下一個sum為1的位置一定為r,即[l,r)之間不存在sum為0或1的位置 for(int j=l;j<=r;j++) t+=sum[j]==2; if(t>ans) ans=t,L=l,R=r; } printf("%d\n%d %d\n",ans,(L+pos-1)%n+1,(R+pos-1)%n+1); return 0; }