首先预处理出$f[i][j][k]$表示长度为$i$的序列,第一个位置是$j$,最后一个位置是$k$时合法的方案数。
从后往前枚举LCP以及那个位置应该改成什么。
用线段树维护区间内最左最右的已经确定的位置,以及区间内的合法方案数。
合并的时候只需要将左右儿子的答案乘起来,然后再乘以左儿子最右到右儿子最左这一段区间的方案数即可。
时间复杂度$O(n\log n)$。
#include<cstdio>
const int N=400010,M=1050000,P=1000000007;
int n,m,i,j,k,x,a[N],g[3][3],f[N][3][3],pos[N],v[N],l[M],r[M],val[M],ans=1;char ch[9],s[N];
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
inline void up(int&x,int y){x+=y;if(x>=P)x-=P;}
void build(int x,int a,int b){l[x]=a,r[x]=b,val[x]=1;if(a==b){pos[a]=x;return;}int mid=(a+b)>>1;build(x<<1,a,mid),build(x<<1|1,mid+1,b);
}
inline bool change(int x,int p){if(~p){if(x>1&&~v[x-1])if(g[v[x-1]][p])return 0;if(x<n&&~v[x+1])if(g[p][v[x+1]])return 0;}v[x]=p,x=pos[x];if(p<0)l[x]=r[x]=0;for(x>>=1;x;x>>=1){l[x]=l[x<<1]?l[x<<1]:l[x<<1|1];r[x]=r[x<<1|1]?r[x<<1|1]:r[x<<1];val[x]=1LL*val[x<<1]*val[x<<1|1]%P;if(r[x<<1]&&l[x<<1|1])val[x]=1LL*val[x]*f[l[x<<1|1]-r[x<<1]+1][v[r[x<<1]]][v[l[x<<1|1]]]%P;}return 1;
}
inline int ask(){int ret=val[1],t,i;if(l[1]>1){for(t=i=0;i<3;i++)up(t,f[l[1]][i][v[l[1]]]);ret=1LL*ret*t%P;}if(r[1]<n){for(t=i=0;i<3;i++)up(t,f[n-r[1]+1][v[r[1]]][i]);ret=1LL*ret*t%P;}return ret;
}
int main(){read(n);for(i=1;i<=n;i++)read(a[i]);read(m);while(m--)scanf("%s",ch),g[ch[0]-'1'][ch[1]-'1']=1;scanf("%s",s+1);for(i=1;i<=n;i++)s[i]-='1',v[i]=s[i];for(i=0;i<3;i++)f[1][i][i]=1;for(i=1;i<n;i++)for(j=0;j<3;j++)for(k=0;k<3;k++)if(f[i][j][k])for(x=0;x<3;x++)if(!g[k][x])up(f[i+1][j][x],f[i][j][k]);build(1,1,n);for(i=n;i;change(a[i--],-1))for(j=0;j<s[a[i]];j++)if(change(a[i],j))up(ans,ask());return printf("%d",ans),0;
}