日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【无码专区7】括号序列(思维)

發布時間:2023/12/3 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【无码专区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=1S?(i?ansi?mod109+7),注意是先取模再相加,相加后并不需要取模。

∣S∣≤107,1s,512MB|S|\le 10^7,1s,512MBS107,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|+1S+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 kixkansxans_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 p1p 一個一個加,所以在桶里面也進行前綴和差分,在 111 位置 +1+1+1,在 p+1p+1p+1 位置 ?1-1?1

每次在二分之前,都得先在 sumisum_isumi? 的桶內把第一個元素丟出去,在丟之前又得把差分數組及時傳遞。

所以在 iii 時,所有桶內都只有 i+1~∣S∣i+1\sim |S|i+1S 的前綴和(這里的前綴和是指 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^6S106


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】括号序列(思维)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。