Subsequence Count (線段樹)
Time Limit: 1000 ms Memory Limit: 256 MB
Description
給定一個01串 $S_{1 \cdots n}$ 和 $Q$ 個操作。
操作有兩種類型:
1、將 $[l, r]$ 區間的數取反(將其中的0變成1,1變成0)。
2、詢問字符串 $S$ 的子串 $S_{l \cdots r}$ 有多少個不同的子序列。由於答案可能很大,請將答案對 $10^9 + 7$ 取模。
在數學中,某個序列的子序列是從最初序列通過去除某些元素但不破壞余下元素的相對位置(在前或在後)而形成的新序列。
Input
第一行包含兩個整數 $N$ 和 $Q$ ,分別表示字符串長度和操作次數。
第二行包含一個字符串 $S$ 。
接下來 $Q$ 行,每行3個整數 $type, l, r$ ,其中 $type$ 表示操作類型, $l, r$ 表示操作區間為 $[l, r]$ 。
Output
對於每一個 $type = 2$ 的詢問,輸出一個整數表示答案。
由於答案可能很大,請將答案對 $10^9 + 7$ 取模。
Sample Input |
Sample Output |
4 4
1010
2 1 4
2 2 4
1 2 3
2 1 4
|
11
6
8
|
HINT
數據範圍與約定
對於5%的數據, $N \leq 20, Q = 1$
對於10%的數據, $N \leq 1000, Q = 1$
對於20%的數據, $N \leq 10^5, Q \leq 10$
對於另外30%的數據, $1 \leq N \leq 10^5, 1 \leq Q \leq 10^5, type = 2$
對於100%的數據, $1 \leq N \leq 10^5, 1 \leq Q \leq 10^5$
題解
這道題很有意思。
首先考慮一下不帶修改的解法。
設$f_{i,0}$表示$s_1...s_i$中,以$0$結尾的子序列數量;$f_{i,1}$表示$s_1...s_i$中,以$1$結尾的子序列數量。
則有方程:
若$s_i$為0:$\begin{aligned}f_{i,0}&=f_{i-1,0}+f_{i-1,1}+1\\f_{i,1}&=f_{i-1,1}\end{aligned}$
若$s_i$為1:$\begin{aligned}f_{i,0}&=f_{i-1,0}\\f_{i,1}&=f_{i-1,0}+f_{i-1,1}+1\end{aligned}$
發現這是一類線性遞推,如果用一個1x3的矩陣表示原來的$f_{i,0}$與$f_{i,1}$:$\begin{pmatrix} f_0&f_1&1 \end{pmatrix}\\$(最後的1僅作為輔助計算),乘上一個3x3的轉移矩陣來得到下一位的狀態呢?
如果序列中這一位$s_i$為0,則在後面乘上這樣一個轉移矩陣$G_0$:
$$\begin{pmatrix} f_0&f_1&1\end{pmatrix}*\begin{pmatrix} 1&0&0\\1&1&0\\1&0&1 \end{pmatrix}=\begin{pmatrix} f_0+f_1+1&f_1&1\end{pmatrix}$$
如果這一位$s_i$為1,則在後面乘上另一個轉移矩陣$G_1$:
$$\begin{pmatrix} f_0&f_1&1\end{pmatrix}*\begin{pmatrix} 1&1&0\\0&1&0\\0&1&1 \end{pmatrix}=\begin{pmatrix} f_0&f_0+f_1+1&1\end{pmatrix}$$
那麽我們用線段樹存儲每一位的轉移矩陣,查詢時直接查詢$[l,r]$的矩陣乘積,乘上初始矩陣(其實初始矩陣為$(0,0,1)$乘了相當於沒乘),所以直接輸出查詢矩陣的$[3][1]+[3][2]$即可
處理區間數值翻轉操作
最基礎的想法就是,將線段樹$[l,r]$葉子節點對應的轉換矩陣換成另一個轉換矩陣。
觀察$G_0=\begin{pmatrix} 1&0&0\\1&1&0\\1&0&1 \end{pmatrix}$ 與$G_1=\begin{pmatrix} 1&1&0\\0&1&0\\0&1&1 \end{pmatrix}$
本質上,只需把第一第二行交換一下,再將第一第二列交換一下,它們都能變成對方。
第一第二行交換,相當於在$G$前乘上一個矩陣$A$。第一第二列交換,相當於在$G$後乘上這個矩陣$A$。
$$A=\begin{pmatrix} 0&1&0\\1&0&0\\0&0&1\end{pmatrix}$$
那麽:
$A*G1*A=G2$ $A*G2*A=G1$
我們暫且看回原來的模型:計算一個矩陣序列。
如果要對$[l,r]$的數字翻轉,假設矩陣序列是$a*b*c*d*e$,考慮如何變換:
按照我們的預想處理方式,那應該變成$(A*a*A)*(A*b*A)*(A*c*A)*(A*d*A)*(A*e*A)$。
此時我們發現,$A*A$居然是單位矩陣...
於是就變成了$A*(a*b*c*d*e)*A$。
相當於對$a*b*c*d*e$直接手動1、2行交換,1、2列交換。
回到線段樹,如果要翻轉,直接在對應區間維護的矩陣進行 行交換列交換,維護並下傳標記即可。
神題啊!
1 #include <cstdio> 2 #include <iostream> 3 using namespace std; 4 typedef long long ll; 5 const int N=100010,Mod=1e9+7; 6 int n,q; 7 char in[N]; 8 struct Mat{ 9 ll a[3][3]; 10 void flip(){ 11 for(int i=0;i<3;i++) swap(a[i][0],a[i][1]); 12 swap(a[0][0],a[1][0]); 13 swap(a[0][1],a[1][1]); 14 } 15 friend Mat operator * (Mat x,Mat y){ 16 Mat ret; 17 for(int i=0;i<3;i++) 18 for(int j=0;j<3;j++){ 19 ret.a[i][j]=0; 20 for(int k=0;k<3;k++) 21 ret.a[i][j]=(ret.a[i][j]+x.a[i][k]*y.a[k][j])%Mod; 22 } 23 return ret; 24 } 25 }; 26 const Mat stand[2]={{1,0,0,1,1,0,1,0,1},{1,1,0,0,1,0,0,1,1}}; 27 struct SegmentTree{ 28 int root,cnt,ch[N*4][2],rev[N*4]; 29 Mat info[N*4]; 30 void build(int &u,int l,int r){ 31 if(!u) u=++cnt; 32 if(l==r){ 33 info[u]=stand[in[l]==‘1‘]; 34 return; 35 } 36 int mid=(l+r)>>1; 37 build(ch[u][0],l,mid); 38 build(ch[u][1],mid+1,r); 39 pushup(u); 40 } 41 void flip(int u,int l,int r,int L,int R){ 42 if(L<=l&&r<=R){ 43 rev[u]^=1; 44 info[u].flip(); 45 return; 46 } 47 pushdown(u); 48 int mid=(l+r)>>1; 49 if(L<=mid) flip(ch[u][0],l,mid,L,R); 50 if(mid<R) flip(ch[u][1],mid+1,r,L,R); 51 pushup(u); 52 } 53 Mat query(int u,int l,int r,int L,int R){ 54 if(L<=l&&r<=R) return info[u]; 55 pushdown(u); 56 int mid=(l+r)>>1; 57 if(R<=mid) return query(ch[u][0],l,mid,L,R); 58 if(mid<L) return query(ch[u][1],mid+1,r,L,R); 59 return query(ch[u][0],l,mid,L,R)*query(ch[u][1],mid+1,r,L,R); 60 } 61 inline void pushup(int u){ 62 info[u]=info[ch[u][0]]*info[ch[u][1]]; 63 } 64 inline void pushdown(int u){ 65 if(!rev[u]) return; 66 rev[ch[u][0]]^=1; rev[ch[u][1]]^=1; 67 info[ch[u][0]].flip(); info[ch[u][1]].flip(); 68 rev[u]=0; 69 } 70 }seg; 71 int main(){ 72 scanf("%d%d%s",&n,&q,in+1); 73 seg.build(seg.root,1,n); 74 int t,l,r; 75 while(q--){ 76 scanf("%d%d%d",&t,&l,&r); 77 if(t==1) 78 seg.flip(seg.root,1,n,l,r); 79 else{ 80 Mat ans=seg.query(seg.root,1,n,l,r); 81 printf("%lld\n",(ans.a[2][0]+ans.a[2][1])%Mod); 82 } 83 } 84 return 0; 85 }奇妙代碼
Subsequence Count (線段樹)