【无码专区7】括号序列(思维)
因為只有std,沒有自我實現,所以是無碼專區
主要是為了訓練思維能力
solution才是dls正解,但是因為只有潦草幾句,所以大部分會有我自己基于正解上面的算法實現過程,可能選擇的算法跟std中dls的實現不太一樣。
std可能也會帶有博主自己的注釋。
problem
給定一個由左右括號構成的字符串 sss,對于每一個位置 iii,定義 ansi:ans_i:ansi?: 有多少個子串滿足這個子串是一個合法的括號序列,并且 iii 這個位置在子串中。
合法的括號序列定義:
- 空串合法。
- 如果 SSS 合法,那么 (S)(S)(S) 也是合法的。
- 如果 S,TS,TS,T 合法,那么 STSTST 也是合法的。
由于答案很大,輸出 ∑i=1∣S∣(i?ansimod109+7)\sum_{i=1}^{|S|}\Big(i·ans_i\mod{10^9+7}\Big)∑i=1∣S∣?(i?ansi?mod109+7),注意是先取模再相加,相加后并不需要取模。
∣S∣≤107,1s,512MB|S|\le 10^7,1s,512MB∣S∣≤107,1s,512MB。
my idea
套路的,將括號序列轉化為 ±1±1±1 分數序列,左括號為 +1+1+1,右括號為 ?1-1?1。并求前綴和
當一個串為合法括號序列,當且僅當每個位置的前綴和都不小于 000 ,且最后一個位置的前綴和恰好為 000。
保證了沒有中途右括號個數多于左括號個數,以及最后左右括號個數匹配,顯然這是充要條件。
考慮枚舉合法括號序列的左端點。
可以肯定的是,被枚舉的位置 iii 本身首先得是個 (。
通過某種手段快速找到 iii 后面的第一個小于 000 的前綴和位置 jjj。因為從 jjj 開始后面就一定不合法了。
注意以下的前綴和若無特殊說明,都是指以 iii 開始的前綴和,所以應該滿足 sumj?sumi?1<0sum_j-sum_{i-1}<0sumj??sumi?1?<0。
顯然,jjj 位置一定是個 ),且 [i,j)[i,j)[i,j) 一定是一個合法括號序列。當然這個性質沒有什么用。有用的只是 jjj 的位置。
特殊地,如果一直到最后 ∣S∣|S|∣S∣ 都沒找到,就返回 ∣S∣+1|S|+1∣S∣+1 即可。
考慮二分,但很容易發現這并不具有連續性,錯誤。
考慮線段樹,相當于在 iii 時去考慮小于 sumi?1sum_{i-1}sumi?1? 的 sumjsum_jsumj?。
具體而言:
從后往前做,到 iii 時線段樹上儲存的是 [i,n][i,n][i,n] 的前綴和(此前綴和是指從 111 開始的)。
線段樹是權值線段樹,每個葉子權值儲存的是距離 iii 最近的下標 jjj。
所以查詢就是在線段樹上 [1,sumi?1)[1,sum_{i-1})[1,sumi?1?) 區間查詢最小的節點權值。
即以權值為線段樹上的下標,以下標為線段樹上的權值。
維護區間最小值。
修改:每次 iii 前移 ?1-1?1,就把 iii 的前綴和對應的線段樹上葉子節點權值更改為 iii。
確定了整個大區間,考慮其中的的合法序列,即 s[i,k],i<k<js[i,k],i<k<js[i,k],i<k<j 的合法括號序列。
顯然只需要滿足 kkk 的前綴和為 000 即可。
現在的問題是怎么迅速找到這些 kkk,并快速計算貢獻。
先考慮快速計算貢獻,顯然 [i,k][i,k][i,k] 一段合法括號序列會讓 i≤x≤ki\le x\le ki≤x≤k 的 ansxans_xansx? 都 +1+1+1。
所以這里采取差分手段,對 ansk+1,ansi?1?1ans_k+1,ans_{i-1}-1ansk?+1,ansi?1??1,最后的時候來一次后綴和,每個 ansxans_xansx? 都是正確的了。
最后就只剩下迅速找到這些 kkk 的問題了。
一開始就將所有的前綴和 sumisum_isumi?(這里的前綴和是指以 111 開始的)桶排序。
即以 sumisum_isumi? 為桶下標分裝所有的前綴和,用 vector 存儲,桶內放的是前綴和下標。
然后從前往后考慮左端點 iii,找到 sumisum_isumi? 的那個桶,以 jjj 為分解線二分出最大的小于 jjj 的位置 ppp,然后桶內前 ppp 個的點對應的 ansansans 都可以差分 +1+1+1。
顯然也不能枚舉 1~p1\sim p1~p 一個一個加,所以在桶里面也進行前綴和差分,在 111 位置 +1+1+1,在 p+1p+1p+1 位置 ?1-1?1。
每次在二分之前,都得先在 sumisum_isumi? 的桶內把第一個元素丟出去,在丟之前又得把差分數組及時傳遞。
所以在 iii 時,所有桶內都只有 i+1~∣S∣i+1\sim |S|i+1~∣S∣ 的前綴和(這里的前綴和是指 111 開始的),并且桶內的差分是更新過的,
意思就是如果要扔去某個桶的對頭 xxx(桶內存的是下標),對頭下一個是 yyy,那么差分數組更新一下 ansy+=ansxans_y+=ans_xansy?+=ansx?。
因為第二個部分的桶是從前往后做,第一個部分是從后往前做,順序不同所以要分開做。
桶內前綴差分,差分完后,桶外后綴差分。
時間復雜度:O(nlog?n)O(n\log n)O(nlogn)。
只能通過 70%70\%70% 的數據點 ∣S∣≤106|S|\le 10^6∣S∣≤106。
solution
凸(艸皿艸 ) O(nlog?n)O(n\log n)O(nlogn) 尼瑪思維代碼量比 O(n)O(n)O(n) 還難。
首先處理一下哪些括號是匹配的,用棧來做。
w(X()X(Z()Z)X(Y()Y()Y)X()X)W ,把括號之間的間隔標號,對于一堆匹配的括號,即合法括號序列,要求兩端的標號相同。
第一次見這種處理,是小生孤陋寡聞了Orzqwqqwq
問題轉化為,有若干個標號,要統計位置 iii 兩端有多少對標號是相同的。
dls題解就給個思路,還是得自己仔細想具體代碼算法實現。qwq
對于每個匹配括號 ()()(),對于左括號 iii ,記錄一個 upiup_iupi?,upiup_iupi? 是最小的能包含這對匹配括號的另一對匹配括號的左括號位置。
即 (..(..)...),第一個左括號就是 upiup_iupi?,這期間要保證不存在 upi<x<i,match(i)<y<match(upi),x,yup_i<x<i,match(i)<y<match(up_i),x,yupi?<x<i,match(i)<y<match(upi?),x,y 能匹配。
類似前綴和差分,ansupi→ansians_{up_i}\rightarrow ans_iansupi??→ansi?。
用 ai,bi:a_i,b_i:ai?,bi?: 記錄左右的連續可完美匹配的括號。
即 (..()..)(..)(..) 第三個左括號和第三個右括號假設是現在 iii 對應的匹配,那么可以選擇第一個左括號到第三個右括號,第一個左括號到第四個右括號。
這就用 ai?bmatch(i)a_i*b_{match(i)}ai??bmatch(i)? 來統計,具體實現可以看 std。
時間復雜度: O(n)O(n)O(n)。
std
#include <bits/stdc++.h> #define LL long long #define LD long double #define ull unsigned long long #define fi first #define se second #define mk make_pair #define PLL pair<LL, LL> #define PLI pair<LL, int> #define PII pair<int, int> #define SZ(x) ((int)x.size()) #define ALL(x) (x).begin(), (x).end() #define fio ios::sync_with_stdio(false); cin.tie(0);using namespace std;const int N = 1e7 + 7; const int inf = 0x3f3f3f3f; const LL INF = 0x3f3f3f3f3f3f3f3f; const int mod = 1e9 + 7; const double eps = 1e-8; const double PI = acos(-1);template<class T, class S> inline void add(T &a, S b) {a += b;if (a >= mod)a -= mod; }template<class T, class S> inline void sub(T &a, S b) {a -= b;if (a < 0)a += mod; }template<class T, class S> inline bool chkmax(T &a, S b) {return a < b ? a = b, true : false; }template<class T, class S> inline bool chkmin(T &a, S b) {return a > b ? a = b, true : false; }mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());int n; int stk[N], top; int match[N], a[N], b[N], up[N]; int ans[N]; char s[N];int main() {freopen("bracket.in", "r", stdin);freopen("bracket.out", "w", stdout);scanf("%s", s + 1);n = strlen(s + 1);top = 0;for (int i = 1; i <= n; i++) {if (s[i] == '(') { //左右括號匹配 up[i]:i位置前最靠近i的還未匹配的左括號/*因為up[i]是還未匹配的,所以up[i]匹配的右括號一定是包含i的(如果能匹配的話)即長相為 (..(..)...) */up[i] = stk[top];stk[++top] = i;} else if (top) {match[i] = stk[top];match[stk[top]] = i;top--;}}for (int i = 1; i <= n; i++) {if (!match[i] || s[i] == '(')continue;b[i] = b[match[i] - 1] + 1; //b[i]標號右括號 b[i]:i位置右括號的標記應為匹配的左括號前一個位置的標號+1}for (int i = n; i >= 1; i--) {if (!match[i] || s[i] == ')')continue;a[i] = a[match[i] + 1] + 1;//a[i]標號左括號 a[i]:i位置左括號的標記應為匹配的右括號后一個位置的標號+1} //+1表示只選i-j這對匹配括號for (int i = 1; i <= n; i++) {if (!match[i] || s[i] == ')')continue;ans[i] = 1LL * a[i] * b[match[i]] % mod;if (up[i])add(ans[i], ans[up[i]]);ans[match[i]] = ans[i];}LL ret = 0;for (int i = 1; i <= n; i++) {ret += 1LL * ans[i] * i % mod;}printf("%lld\n", ret);return 0; }總結
以上是生活随笔為你收集整理的【无码专区7】括号序列(思维)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鼋怎么读 鼋是什么字
- 下一篇: 铺地毯(矩形的交+前后缀矩形交)