BZOJ3019 : [Balkan2012]handsome

首先预处理出$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;
}

  

posted @ 2017-02-06 18:17  Claris  阅读(214)  评论(0编辑  收藏  举报