CF 1150 D Three Religions——序列自动机优化DP
题目:http://codeforces.com/contest/1150/problem/D
老是想着枚举当前在给定字符串的哪个位置,以此来转移。
所以想对三个串分别建 trie 树,然后求出三个trie树上各选一个点的答案。那么从“在三个trie树的根,在给定字符串的0位置”开始扩展。
当然 TLE 了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<queue> #include<map> using namespace std; const int N=1e5+5,M=1005; int n,m,tot=3,nw[5],fa[M],c[M][30],fl[M],q2[M]; char s[N]; struct Dt{ int x,y,z; Dt(int x=0,int y=0,int z=0):x(x),y(y),z(z) {} bool operator< (const Dt &b)const { if(x!=b.x)return x<b.x; if(y!=b.y)return y<b.y; return z<b.z; } bool operator== (const Dt &b)const {return x==b.x&&y==b.y&&z==b.z;} }qr[M],I; struct Node{ Dt a;int ps; Node(Dt a=I,int p=0):a(a),ps(p) {} bool operator< (const Node &b)const {return a<b.a;} }; queue<Node> q; map<Dt,bool> mp; void add(int &x,int w) { if(c[x][w])x=c[x][w];////if!!!!! else{ c[x][w]=++tot; fa[tot]=x; x=tot;} } void get_fl(int rt) { int he=0,tl=0; for(int i=0;i<26;i++) if(c[rt][i])fl[c[rt][i]]=rt,q2[++tl]=c[rt][i]; else c[rt][i]=rt; while(he<tl) { int k=q2[++he],pr=fl[k]; for(int i=0;i<26;i++) if(c[k][i])fl[c[k][i]]=c[pr][i],q2[++tl]=c[k][i]; else c[k][i]=c[pr][i]; } } void cz(Dt cr,int ps) { for(int x=cr.x;x;x=fl[x]) for(int y=cr.y;y;y=fl[y]) for(int z=cr.z;z;z=fl[z]) if(!mp[cr]){mp[cr]=1; q.push(Node(cr,ps));} else return; } int main() { scanf("%d%d",&n,&m); scanf("%s",s+1); nw[1]=1; nw[2]=2; nw[3]=3; char op,w;int d; for(int i=1;i<=m;i++) { cin>>op; scanf("%d",&d); if(op=='+'){cin>>w;add(nw[d],w-'a');} else nw[d]=fa[nw[d]]; qr[i]=Dt(nw[1],nw[2],nw[3]); } for(int i=1;i<=3;i++)get_fl(i); Dt cr=Dt(1,2,3); mp[cr]=1; q.push(Node(cr,0)); while(q.size()) { Node k=q.front();q.pop(); if(k.ps==n)continue; k.ps++; q.push(k); int w=s[k.ps]-'a'; cr=k.a; cr.x=c[cr.x][w]; cz(cr,k.ps); cr=k.a; cr.y=c[cr.y][w]; cz(cr,k.ps); cr=k.a; cr.z=c[cr.z][w]; cz(cr,k.ps); } for(int i=1;i<=m;i++) puts(mp.count(qr[i])?"YES":"NO"); return 0; }
给定字符串的一个位置可能使得三个串都不能扩展。所以考虑不枚举字符串的每个位置来转移,而是做一个序列自动机。
令 dp[i][j][k] 表示三个地区分别匹配了前 i 、j、k 个字符的“最靠前位置”,如果值==n+1说明无解。
那么就可以使用序列自动机实现 2503 的DP了。就是 dp[i][j][k] = min( nxt[ dp[i-1][j][k] ][ c1[i] ] , nxt[ dp[i][j-1][k] ][ c2[j] ] , nxt[ dp[i][j][k-1] ][ c3[k] ] ) 。
对于一个询问,如果是 ' - ' ,DP数组不用改动。如果是 ' + ' ,固定该维, 2502 做一下DP即可。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar(); return fx?ret:-ret; } int Mn(int a,int b){return a<b?a:b;} const int N=1e5+5,M=255,K=26; int n,lst[K],nxt[N][K],dp[M][M][M],l0,l1,l2; char s[N];int c0[M],c1[M],c2[M]; int main() { n=rdn(); int Q=rdn(); scanf("%s",s+1); for(int i=0;i<K;i++) lst[i]=nxt[n+1][i]=n+1; for(int i=n;i>=0;i--) { for(int j=0;j<K;j++) nxt[i][j]=lst[j]; if(i)lst[s[i]-'a']=i; } char op;int x,w; while(Q--) { cin>>op; x=rdn()-1; if(op=='+') { cin>>op; w=op-'a'; if(x==0) { c0[++l0]=w; for(int i=0;i<=l1;i++) for(int j=0;j<=l2;j++) { dp[l0][i][j]=nxt[dp[l0-1][i][j]][w]; if(i)dp[l0][i][j]= Mn(dp[l0][i][j],nxt[dp[l0][i-1][j]][c1[i]]);//i not l1 if(j)dp[l0][i][j]= Mn(dp[l0][i][j],nxt[dp[l0][i][j-1]][c2[j]]); } } if(x==1) { c1[++l1]=w; for(int i=0;i<=l0;i++) for(int j=0;j<=l2;j++) { dp[i][l1][j]=nxt[dp[i][l1-1][j]][w]; if(i)dp[i][l1][j]= Mn(dp[i][l1][j],nxt[dp[i-1][l1][j]][c0[i]]); if(j)dp[i][l1][j]= Mn(dp[i][l1][j],nxt[dp[i][l1][j-1]][c2[j]]); } } if(x==2) { c2[++l2]=w; for(int i=0;i<=l0;i++) for(int j=0;j<=l1;j++) { dp[i][j][l2]=nxt[dp[i][j][l2-1]][w]; if(i)dp[i][j][l2]= Mn(dp[i][j][l2],nxt[dp[i-1][j][l2]][c0[i]]); if(j)dp[i][j][l2]= Mn(dp[i][j][l2],nxt[dp[i][j-1][l2]][c1[j]]); } } } else { if(x==0)l0--; if(x==1)l1--; if(x==2)l2--; } puts(dp[l0][l1][l2]<=n?"YES":"NO"); } return 0; }