<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【學習筆記】AC自動機

      AC自動機是以trie結構為基礎,結合KMP算法思想構建的,用于解決多模式串匹配問題。
      它的構建方式分為以下幾步:
      \(1.\) 建立trie樹
      \(2.\) 構建失配(fail)指針
      其中 fail 指針指向的是當前節點的狀態的后綴所對應的狀態。

      這里明確一下,trie樹中的每個節點表示的是一個狀態,及某個模式串的前綴。
      因此若這個狀態可以在文本串中匹配,則它的后綴必定也能在文本串中匹配。

      構建 fail 指針的過程是一個 bfs,對于每個點,用其父親的 fail 來更新自己的 fail
      即,父親的后綴加上自己當前的這一位字符,就是自己的后綴了。
      核心代碼:

      for(int i=0;i<=25;i++){
      	if(tr[0][i]){
      		q.push(tr[0][i]);
      	}
      }
      while(q.size()){
      	int u=q.front();
      	q.pop();
      	for(int i=0;i<=25;i++){
      		if(tr[u][i]){
      			fail[tr[u][i]]=tr[fail[u]][i];
      			q.push(tr[u][i]);
      		}
      		else{ // 重構trie樹結構
      			tr[u][i]=tr[fail[u]][i];
      		}
      	}
      }
      

      代碼中有一點是沒有提到的,就是加了注釋的那一句。即,若沒有 u+(i+'a') 這個狀態,就讓這個狀態指向這個狀態的后綴。于是在前面那句 if 和以后的匹配中,可以避免 while 循環不停跳 fail 的情況。

      板子題:HDU 2222
      匹配時不斷跳 fail 就行了,即若這個狀態能匹配,則它的后綴也能匹配。注意打標記避免重復匹配,否則時間復雜度無法保證。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define read(x){\
      	char ch;\
      	int fu=1;\
      	while(!isdigit(ch=getchar()))\
      		fu-=(ch=='-')<<1;\
      	x=ch&15;\
      	while(isdigit(ch=getchar()))\
      		x=(x<<1)+(x<<3)+(ch&15);\
      	x*=fu;\
      }
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=1e4+5,maxm=1e6+5;
      int T,n,tot,tr[maxn*50][30];
      int fail[maxn*50],end[maxn*50];
      char s[maxm];
      queue<int> q;
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	read(T);
      	while(T--){
      		tot=0;
      		fail[0]=end[0]=0;
      		for(int i=0;i<=25;i++){
      			tr[0][i]=0;
      		}
      		read(n);
      		while(n--){
      			scanf(" %s",s+1);
      			int len=strlen(s+1);
      			int p=0;
      			for(int i=1;i<=len;i++){
      				int d=s[i]-'a';
      				if(!tr[p][d]){
      					tr[p][d]=++tot;
      					fail[tot]=end[tot]=0;
      					for(int j=0;j<=25;j++){
      						tr[tot][j]=0;
      					}
      				}
      				p=tr[p][d];
      			}
      			end[p]++;
      		}
      		for(int i=0;i<=25;i++){
      			if(tr[0][i]){
      				q.push(tr[0][i]);
      			}
      		}
      		while(q.size()){
      			int u=q.front();
      			q.pop();
      			for(int i=0;i<=25;i++){
      				if(tr[u][i]){
      					fail[tr[u][i]]=tr[fail[u]][i];
      					q.push(tr[u][i]);
      				}
      				else{
      					tr[u][i]=tr[fail[u]][i];
      				}
      			}
      		}
      		scanf(" %s",s+1);
      		n=strlen(s+1);
      		int res=0,p=0;
      		for(int i=1;i<=n;i++){
      			p=tr[p][s[i]-'a'];
      			for(int j=p;j&&~end[j];j=fail[j]){
      				res+=end[j];
      				end[j]=-1;
      			}
      		}
      		printf("%d\n",res);
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      Luogu P5231
      這里就要注意到,trie樹上本來的從父親連向兒子的邊,和構建 fail 指針時為了方便重構的邊的區別了。在代碼中,后者用 \(tra\) 表示。
      每個點都是它的子節點的前綴,于是可以將父親節點的答案(如果有的話)傳給兒子節點,最后在葉子節點統計答案即可。因為是動態開點,每個點的子節點編號肯定比它自己大,因此不用 dfs 遍歷整棵樹,直接順序遍歷所有節點就可以了。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define read(x){\
      	char ch;\
      	int fu=1;\
      	while(!isdigit(ch=getchar()))\
      		fu-=(ch=='-')<<1;\
      	x=ch&15;\
      	while(isdigit(ch=getchar()))\
      		x=(x<<1)+(x<<3)+(ch&15);\
      	x*=fu;\
      }
      #define mp make_pair
      #define lwrb lower_bound
      #define uprb upper_bound
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=1e7+5,maxm=1e5+5;
      int n,m,tot,tr[maxn][4],fail[maxn];
      int dep[maxn],zhi[maxn],ans[maxm];
      int tra[maxn][4];
      char s[maxn],t[105];
      multimap<int,int> hao;
      queue<int> q;
      il int gid(char x){
      	if(x=='E'){
      		return 0;
      	}
      	if(x=='S'){
      		return 1;
      	}
      	if(x=='W'){
      		return 2;
      	}
      	return 3;
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	read(n)read(m);
      	scanf(" %s",s+1);
      	for(int i=1;i<=m;i++){
      		scanf(" %s",t+1);
      		int p=0,len=strlen(t+1);
      		for(int j=1;j<=len;j++){
      			int d=gid(t[j]);
      			if(!tr[p][d]){
      				tra[p][d]=tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      			dep[p]=j;
      		}
      		hao.insert(mp(p,i));
      	}
      	for(int i=0;i<=3;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=3;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tra[fail[u]][i];
      				q.push(tr[u][i]);
      			}
      			else{
      				tra[u][i]=tra[fail[u]][i];
      			}
      		}
      	}
      	int p=0;
      	for(int i=1;i<=n;i++){
      		p=tra[p][gid(s[i])];
      		for(int j=p;j&&!zhi[j];j=fail[j]){
      			zhi[j]=dep[j];
      		}
      	}
      	for(int i=1;i<=tot;i++){
      		bool flag=0;
      		for(int j=0;j<=3;j++){
      			if(tr[i][j]){
      				flag=1;
      				zhi[tr[i][j]]=max(zhi[tr[i][j]],zhi[i]);
      			}
      		}
      		if(!flag){
      			auto l=hao.lwrb(i);
      			auto r=hao.uprb(i);
      			while(l!=r){
      				ans[l++->second]=zhi[i];
      			}
      		}
      	}
      	for(int i=1;i<=m;i++){
      		printf("%d\n",ans[i]);
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      然后我突然發現了一個問題,如果有一個模式串是另一個模式串的前綴的話,那它的結尾就不是葉子節點,就會無法統計到答案。比如下面這組數據:

      4 2
      NNSS
      NN
      NNS
      

      剛才這篇代碼會對第一個模式串輸出 0。因此需要在每個節點記一個 end,然后把統計答案的判斷條件改為 end[i]。(為什么不在每個節點都統計一遍答案呢,因為時間會達到 \(O(n\log m)\),有可能會炸。)然而洛谷上顯然并沒有這樣的數據。
      真正正確的代碼:

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define read(x){\
      	char ch;\
      	int fu=1;\
      	while(!isdigit(ch=getchar()))\
      		fu-=(ch=='-')<<1;\
      	x=ch&15;\
      	while(isdigit(ch=getchar()))\
      		x=(x<<1)+(x<<3)+(ch&15);\
      	x*=fu;\
      }
      #define mp make_pair
      #define lwrb lower_bound
      #define uprb upper_bound
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=1e7+5,maxm=1e5+5;
      int n,m,tot,tr[maxn][4],fail[maxn];
      int dep[maxn],zhi[maxn],ans[maxm];
      int tra[maxn][4];
      char s[maxn],t[105];
      multimap<int,int> hao;
      queue<int> q;
      bitset<maxn> end;
      il int gid(char x){
      	if(x=='E'){
      		return 0;
      	}
      	if(x=='S'){
      		return 1;
      	}
      	if(x=='W'){
      		return 2;
      	}
      	return 3;
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	read(n)read(m);
      	scanf(" %s",s+1);
      	for(int i=1;i<=m;i++){
      		scanf(" %s",t+1);
      		int p=0,len=strlen(t+1);
      		for(int j=1;j<=len;j++){
      			int d=gid(t[j]);
      			if(!tr[p][d]){
      				tra[p][d]=tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      			dep[p]=j;
      		}
      		end[p]=1;
      		hao.insert(mp(p,i));
      	}
      	for(int i=0;i<=3;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=3;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tra[fail[u]][i];
      				q.push(tr[u][i]);
      			}
      			else{
      				tra[u][i]=tra[fail[u]][i];
      			}
      		}
      	}
      	int p=0;
      	for(int i=1;i<=n;i++){
      		p=tra[p][gid(s[i])];
      		for(int j=p;j&&!zhi[j];j=fail[j]){
      			zhi[j]=dep[j];
      		}
      	}
      	for(int i=1;i<=tot;i++){
      		for(int j=0;j<=3;j++){
      			if(tr[i][j]){
      				zhi[tr[i][j]]=max(zhi[tr[i][j]],zhi[i]);
      			}
      		}
      		if(end[i]){
      			auto l=hao.lwrb(i);
      			auto r=hao.uprb(i);
      			while(l!=r){
      				ans[l++->second]=zhi[i];
      			}
      		}
      	}
      	for(int i=1;i<=m;i++){
      		printf("%d\n",ans[i]);
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      /*
      4 2
      NNSS
      NN
      NNS
      */
      

      一本通 1482
      計算出現次數,那也不難。只需給匹配到的每個點的出現次數都加一,最后對于每個模式串對應到字典樹上的編號查詢即可。這樣做的復雜度是 \(O(n|S|)\) 的,過不去。于是考慮只給 fail 鏈的鏈頭加上貢獻,再跑一遍拓撲排序即可。(因為 fail 指針連成的鏈不可能有環。)
      另外,上面那道題用 multimap 存儲字典樹的節點對應的模式串編號的方式非常的蠢,直接開個數組存每個模式串對應的節點編號就行了。否則在一本通上會喜提RE+WA。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=1e6+5;
      int n,len[205],fail[maxn],num[maxn];
      int tot,tr[maxn][30],deg[maxn],hao[205];
      string s[205];
      queue<int> q;
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>n;
      	for(int i=1;i<=n;i++){
      		cin>>s[i];
      		len[i]=s[i].size();
      		s[i]=" "+s[i];
      		int p=0;
      		for(int j=1;j<=len[i];j++){
      			int d=s[i][j]-'a';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		hao[i]=p;
      	}
      	for(int i=0;i<=25;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				if(fail[tr[u][i]]){
      					deg[fail[tr[u][i]]]++;
      				}
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      	for(int i=1;i<=n;i++){
      		int p=0;
      		for(int j=1;j<=len[i];j++){
      			p=tr[p][s[i][j]-'a'];
      			num[p]++;
      		}
      	}
      	for(int i=1;i<=tot;i++){
      		if(!deg[i]){
      			q.push(i);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		int v=fail[u];
      		if(!v){
      			continue;
      		}
      		num[v]+=num[u];
      		if(--deg[v]==0){
      			q.push(v);
      		}
      	}
      	for(int i=1;i<=n;i++){
      		cout<<num[hao[i]]<<"\n";
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      Luogu P2444
      題目意思是要找到一個無法匹配任何模式串的無限長的文本串。
      不難發現,這樣的串在AC自動機上匹配時會跑出一個環。
      因此只需要在AC自動機上找有沒有不經過任何模式串的結尾節點的環就可以了。
      但是這里會有問題:某個點或許不是模式串的結尾,但是它的后綴有可能是。
      于是在求 fail 時,如果這個點的 fail 是結尾節點,那么就把它也設為結尾節點(dfs時不能經過它)。
      代碼不難寫,但會TLE。
      原因是由于要找環,在遍歷完這個點的子樹后就將這個點的訪問狀態(vis2 數組)改成0了,但在之后再遍歷這個點是沒有意義的。
      因此就再記一個 vis1 數組,記錄是否遍歷過它就行了。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define read(x){\
      	char ch;\
      	int fu=1;\
      	while(!isdigit(ch=getchar()))\
      		fu-=(ch=='-')<<1;\
      	x=ch&15;\
      	while(isdigit(ch=getchar()))\
      		x=(x<<1)+(x<<3)+(ch&15);\
      	x*=fu;\
      }
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=3e4+5;
      int n,tot,tr[maxn][2],fail[maxn];
      char s[maxn];
      bitset<maxn> end,vis1,vis2;
      queue<int> q;
      il void dfs(int u){
      	vis1[u]=vis2[u]=1;
      	for(int i=0;i<=1;i++){
      		if(vis2[tr[u][i]]){
      			puts("TAK");
      			exit(0);
      		}
      		if(end[tr[u][i]]||vis1[tr[u][i]]){
      			continue;
      		}
      		dfs(tr[u][i]);
      	}
      	vis2[u]=0;
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	read(n);
      	while(n--){
      		scanf(" %s",s+1);
      		int len=strlen(s+1),p=0;
      		for(int i=1;i<=len;i++){
      			int d=s[i]&15;
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		end[p]=1;
      	}
      	for(int i=0;i<=1;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=1;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				if(end[fail[tr[u][i]]]){
      					end[tr[u][i]]=1;
      				}
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      //	for(int i=1;i<=tot;i++){
      //		cout<<tr[i][0]<<" "<<tr[i][1]<<" "<<fail[i]<<"\n";
      //	}
      	dfs(0);
      	puts("NIE");
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      [HNOI2006] 最短母串問題
      要求字典序最小,考慮一位一位貪心,bfs。先建出 AC 自動機,然后 bfs 同時記錄路徑即可。需要狀壓。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define pii pair<int,int>
      #define mp make_pair
      #define fir first
      #define sec second
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=605,maxm=(1<<12)+5;
      int n,tr[maxn][30],tot;
      int fail[maxn],sta[maxn];
      pair<pii,int> pre[maxn][maxm];
      bool vis[maxn][maxm];
      char ans[maxn];
      string s;
      queue<int> q;
      queue<pii> q2;
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      //	cout<<cplx::usdmem();
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>n;
      	for(int i=1,p;i<=n;i++){
      		cin>>s;
      		p=0;
      		for(int j=0,d;j<s.size();j++){
      			d=s[j]-'A';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		sta[p]|=1<<(i-1);
      	}
      	for(int i=0;i<=25;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				sta[tr[u][i]]|=sta[fail[tr[u][i]]];
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      	vis[0][0]=1,q2.push(mp(0,0));
      //	puts("666");
      	while(q2.size()){
      		int u=q2.front().fir,S=q2.front().sec;
      //		puts("666");
      //		cout<<u<<"\n";
      		q2.pop();
      //		puts("777");
      		if(S==((1<<n)-1)){
      			int cnt=0;
      			while(u){
      //				puts("666");
      //				cout<<u<<"\n";
      				ans[++cnt]='A'+pre[u][S].sec;
      				pii tmp=pre[u][S].fir;
      				u=tmp.fir,S=tmp.sec;
      			}
      			for(int i=cnt;i;i--){
      				cout<<ans[i];
      			}
      			return 0;
      		}
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]&&!vis[tr[u][i]][S|sta[tr[u][i]]]){
      				vis[tr[u][i]][S|sta[tr[u][i]]]=1;
      				pre[tr[u][i]][S|sta[tr[u][i]]]=mp(mp(u,S),i);
      				q2.push(mp(tr[u][i],S|sta[tr[u][i]]));
      			}
      		}
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      「一本通 2.4 練習 6」文本生成器
      正難則反,設 \(dp_{i,j}\) 表示到 \(i\) 點串長為 \(j\) 的方案數,從父親向兒子轉移即可。注意轉移順序,應該先枚舉 \(i\)

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=6e3+5,mod=1e4+7;
      int n,m,tot,tr[maxn][30];
      int fail[maxn],dp[maxn][105];
      bool end[maxn];
      queue<int> q;
      string s;
      il int qpow(int x,int y){
      	int res=1;
      	while(y){
      		if(y&1){
      			res=res*x%mod;
      		}
      		y>>=1,x=x*x%mod;
      	}
      	return res;
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>n>>m;
      	for(int i=1,p;i<=n;i++){
      		cin>>s;
      		p=0;
      		for(int j=0,d;j<s.size();j++){
      			d=s[j]-'A';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		end[p]=1;
      	}
      	for(int i=0;i<=25;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				if(end[fail[tr[u][i]]]){
      					end[tr[u][i]]=1;
      				}
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      	dp[0][0]=1;
      	for(int i=1;i<=m;i++){
      		for(int j=0;j<=tot;j++){
      			for(int k=0;k<=25;k++){
      				if(!end[tr[j][k]]){
      					(dp[tr[j][k]][i]+=dp[j][i-1])%=mod;
      				}
      			}
      		}
      	}
      	int ans=0;
      	for(int i=0;i<=tot;i++){
      		(ans+=dp[i][m])%=mod;
      	}
      	cout<<(qpow(26,m)-ans+mod)%mod;
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      [BZOJ 2905]背單詞
      \(dp_i\) 表示前 \(i\) 個字符串的最大答案。則 \(i\) 只能從它的子串轉移。考慮子串本質就是從前面刪幾個字符再從后面刪幾個字符串,具體到 AC 自動機上就是 \(i\) 的某個前綴跳了若干個 fail。于是我們從 \(fail_i\)\(i\) 連邊,這樣就建出了一棵 fail 樹,對于每個前綴去查詢根鏈[1]的最大值,然后再更新。用線段樹維護即可。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      #define pb push_back
      #define lid id<<1
      #define rid id<<1|1
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=3e5+5,inf=0x3f3f3f3f;
      int T,n,tr[maxn][30];
      int dfn[maxn],sz[maxn];
      int fail[maxn],hao[maxn];
      int fa[maxn],cnt,a[maxn];
      vector<int> e[maxn];
      string s;
      queue<int> q;
      il void dfs(int u){
      //	cout<<u<<"\n";
      	dfn[u]=++cnt;
      	sz[u]=1;
      	for(int v:e[u]){
      		dfs(v);
      		sz[u]+=sz[v];
      	}
      }
      struct{
      	int tr[maxn<<2];
      	il void build(int id,int l,int r){
      		tr[id]=0;
      		if(l==r){
      			return ;
      		}
      		int mid=(l+r)>>1;
      		build(lid,l,mid);
      		build(rid,mid+1,r);
      	}
      	il void upd(int id,int L,int R,int l,int r,int v){
      		if(L>=l&&R<=r){
      			tr[id]=max(tr[id],v);
      			return ;
      		}
      		int mid=(L+R)>>1;
      		if(l<=mid){
      			upd(lid,L,mid,l,r,v);
      		}
      		if(r>mid){
      			upd(rid,mid+1,R,l,r,v);
      		}
      	}
      	il int query(int id,int l,int r,int p){
      		int res=tr[id];
      		if(l==r){
      			return res;
      		}
      		int mid=(l+r)>>1;
      		return max(res,p<=mid?query(lid,l,mid,p):query(rid,mid+1,r,p));
      	}
      }SG;
      il void solve(){
      	cin>>n;
      	int tot=0;
      	for(int i=0;i<=25;i++){
      		tr[0][i]=0;
      	}
      	for(int i=1,p;i<=n;i++){
      		cin>>s>>a[i];
      		p=0;
      		for(int j=0,d;j<s.size();j++){
      			d=s[j]-'a';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      				for(int k=0;k<=25;k++){
      					tr[tot][k]=0;
      				}
      				fa[tot]=p;
      			}
      			p=tr[p][d];
      		}
      		hao[i]=p;
      	}
      	for(int i=0;i<=25;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      //		cout<<u<<"\n";
      		q.pop();
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      //	cout<<tot<<"\n";
      	for(int i=1;i<=tot;i++){
      		e[fail[i]].pb(i);
      	}
      	cnt=0;
      	dfs(0);
      //	for(int i=0;i<=tot;i++){
      //		cout<<i<<" "<<dfn[i]<<" "<<sz[i]<<"\n";
      //	}
      	SG.build(1,1,cnt);
      	int ans=0;
      	for(int i=1,res,p;i<=n;i++){
      		p=hao[i],res=0;
      //		cout<<i<<"-\n";
      		while(p){
      //			cout<<p<<"\n";
      			res=max(res,SG.query(1,1,cnt,dfn[p]));
      			p=fa[p];
      		}
      		res=max(res,res+a[i]);
      		ans=max(ans,res);
      		SG.upd(1,1,cnt,dfn[hao[i]],dfn[hao[i]]+sz[hao[i]]-1,res);
      //		cout<<res<<"\n";
      	}
      	cout<<ans<<"\n";
      	for(int i=0;i<=tot;i++){
      		fa[i]=fail[i]=0;
      		e[i].clear();
      	}
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      //	freopen("433.in","r",stdin);
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>T;
      	while(T--){
      		solve();
      	}
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      [JSOI2009]密碼
      \(dp_{i,j,S}\) 表示填了 \(i\) 位,在 AC 自動機上的 \(j\) 號節點,當前覆蓋的字符串集位 \(S\) 的方案數。于是有轉移:

      \[\large{dp_{i,j,S}\to dp_{i+1,tr_{j,k},S\operatorname{or}sta_{tr_{j,k}}}} \]

      其中 \(tr_{j,k}\) 表示 AC 自動機上 \(j\) 點加上字符 \(k\) 的節點,\(sta_j\) 表示以 \(j\) 點為結尾的字符串構成的集合,\(\operatorname{or}\) 表示按位或。
      輸出方案,先記憶化搜索確定每個狀態 \((i,j,S)\) 能否轉移到合法狀態,再一遍 dfs 輸出即可。

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      const int maxn=(1<<10)+5;
      int n,m,tr[105][30],tot;
      int fail[105],sta[105];
      ll dp[30][105][maxn];
      bool vis[30][105][maxn];
      bool f[30][105][maxn];
      char ans[maxn];
      string s;
      queue<int> q;
      il bool dfs1(int i,int j,int S){
      	if(vis[i][j][S]){
      		return f[i][j][S];
      	}
      	vis[i][j][S]=1;
      	if(i==m){
      		return f[i][j][S]=S==(1<<n)-1;
      	}
      	bool &res=f[i][j][S];
      	for(int k=0;k<=25;k++){
      		res|=dfs1(i+1,tr[j][k],S|sta[tr[j][k]]);
      	}
      	return res;
      }
      il void dfs2(int i,int j,int S){
      	if(i==m){
      		for(int k=1;k<=m;k++){
      			cout<<ans[k];
      		}
      		cout<<"\n";
      		return ;
      	}
      	for(int k=0;k<=25;k++){
      		if(f[i+1][tr[j][k]][S|sta[tr[j][k]]]){
      			ans[i+1]=k+'a';
      			dfs2(i+1,tr[j][k],S|sta[tr[j][k]]);
      		}
      	}
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>m>>n;
      	for(int i=1,p;i<=n;i++){
      		cin>>s;
      		p=0;
      		for(int j=0,d;j<s.size();j++){
      			d=s[j]-'a';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		sta[p]|=1<<(i-1);
      	}
      	for(int i=0;i<=25;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<=25;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				sta[tr[u][i]]|=sta[fail[tr[u][i]]];
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      	dp[0][0][0]=1;
      	for(int i=0;i<=m;i++){
      		for(int j=0;j<=tot;j++){
      			for(int S=0;S<1<<n;S++){
      				if(!dp[i][j][S]){
      					continue;
      				}
      				for(int k=0;k<=25;k++){
      					dp[i+1][tr[j][k]][S|sta[tr[j][k]]]+=dp[i][j][S];
      				}
      			}
      		}
      	}
      	ll ans=0;
      	for(int i=0;i<=tot;i++){
      		ans+=dp[m][i][(1<<n)-1];
      	}
      	cout<<ans<<"\n";
      	if(ans>42){
      		return 0;
      	}
      	dfs1(0,0,0);
      	dfs2(0,0,0);
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      [bzoj2553]禁忌
      考慮如果暴力 DP 的話,時間復雜度會超標,于是矩陣加速。
      但是在 DP 的過程中,期望又要乘又要加的,用矩陣很難轉移。于是考慮用矩陣去算概率,新開一行去存期望。
      這個貪心是很顯然的:當匹配完了一個單詞時,直接從頭開始嘗試匹配下一個單詞。由于題目要求不能重疊,這不僅是策略上的優化還是正確性的保證。
      具體地,假設要從節點 \(j\) 轉移到 \(k\),如果 \(k\) 是某個單詞的結尾,那就把貢獻加給根,同時加給期望。否則就只能加給 \(k\) 了。
      時間復雜度 \(O((\sum|T_i|)^3\log len)\)

      Code
      #include<bits/stdc++.h>
      #define ll long long
      #define il inline
      using namespace std;
      namespace asbt{
      namespace cplx{bool begin;}
      int n,m,tr[80][30];
      int tot,fail[80];
      double ab;
      bool end[80];
      string s;
      queue<int> q;
      struct juz{
      	double mat[80][80];
      	juz(){
      		for(int i=0;i<=tot;i++){
      			for(int j=0;j<=tot;j++){
      				mat[i][j]=0;
      			}
      		}
      	}
      	il double*operator[](int x){
      		return mat[x];
      	}
      	il juz operator*(juz x)const{
      		juz res;
      		for(int i=0;i<=tot;i++){
      			for(int j=0;j<=tot;j++){
      				for(int k=0;k<=tot;k++){
      					res[i][j]+=mat[i][k]*x[k][j];
      				}
      			}
      		}
      		return res;
      	}
      }bas;
      il juz qpow(juz x,int y){
      	juz res;
      	for(int i=0;i<=tot;i++){
      		res[i][i]=1;
      	}
      	while(y){
      		if(y&1){
      			res=res*x;
      		}
      		y>>=1,x=x*x;
      	}
      	return res;
      }
      namespace cplx{
      	bool end;
      	il double usdmem(){return (&begin-&end)/1048576.0;}
      }
      int main(){
      	ios::sync_with_stdio(0),cin.tie(0);
      	cin>>n>>m>>ab;
      	for(int i=1,p;i<=n;i++){
      		cin>>s;
      		p=0;
      		for(int j=0,d;j<s.size();j++){
      			d=s[j]-'a';
      			if(!tr[p][d]){
      				tr[p][d]=++tot;
      			}
      			p=tr[p][d];
      		}
      		end[p]=1;
      	}
      	for(int i=0;i<ab;i++){
      		if(tr[0][i]){
      			q.push(tr[0][i]);
      		}
      	}
      	while(q.size()){
      		int u=q.front();
      		q.pop();
      		for(int i=0;i<ab;i++){
      			if(tr[u][i]){
      				fail[tr[u][i]]=tr[fail[u]][i];
      				end[tr[u][i]]|=end[fail[tr[u][i]]];
      				q.push(tr[u][i]);
      			}
      			else{
      				tr[u][i]=tr[fail[u]][i];
      			}
      		}
      	}
      //	for(int i=0;i<=tot;i++){
      //		cout<<fail[i]<<" ";
      //	}
      //	puts("");
      	tot++;
      	for(int i=0;i<tot;i++){
      		for(int j=0;j<ab;j++){
      			if(end[tr[i][j]]){
      				bas[i][0]+=1.0/ab;
      				bas[i][tot]+=1.0/ab;
      			}
      			else{
      				bas[i][tr[i][j]]+=1.0/ab;
      			}
      		}
      	}
      	bas[tot][tot]=1;
      	printf("%.10f",qpow(bas,m)[0][tot]);
      	return 0;
      }
      }
      int main(){return asbt::main();}
      

      1. 根鏈:由 2024 陜西省隊隊員馬思博提出,指一棵樹上一個端點在樹根的鏈。(摘自 UKE 的題解) ??

      posted @ 2024-12-08 15:30  zhangxy__hp  閱讀(107)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 久久人人爽人人爽人人av| 欧美巨大极度另类| gogogo高清在线播放免费| 性饥渴少妇AV无码毛片| 久久国产免费观看精品3| 日本一区二区三区在线看 | 亚洲香蕉网久久综合影视| 国产办公室秘书无码精品99| 久久精品国产再热青青青| 日韩中文字幕亚洲精品一| 久热这里只精品99国产6-99RE视…| 亚洲精品一区二区美女| 国产成人精品成人a在线观看| 成人啪精品视频网站午夜| 国产探花在线精品一区二区| 中文字幕一区二区三区久久蜜桃| 亚洲第一香蕉视频啪啪爽| 国产天美传媒性色av高清| a级亚洲片精品久久久久久久| 亚洲精品不卡av在线播放| 亚洲第一狼人成人综合网| 中文国产日韩欧美二视频| 亚洲国产精品久久无人区 | 亚洲www永久成人网站| 欧美大香线蕉线伊人久久| 国产精品久久久久久久9999| 巨爆乳中文字幕爆乳区| 景谷| 国产精品进线69影院| 另类专区一区二区三区| 中卫市| 欧洲亚洲色一区二区色99| 樱花草在线社区WWW韩国| 不卡一区二区国产精品| 亚洲丰满老熟女激情av| 精品国产乱码久久久久APP下载| 亚洲av成人网人人蜜臀| 国产中文字幕在线精品| 少妇被粗大的猛烈进出69影院一| 亚洲国产精品成人av网| 国产午夜一区二区在线观看|