【后缀自动机】ZOJ3891、HDU5343

ZOJ3891 K-hash

出处:ZOJ Monthly, July 2015 - K

题意:给出一个整数K(<=32)和一个长度50000以内由十进制数构成的字符串S,要求统计S的不重复子串所表示的数对K取模的结果。

思路:在SAM上按节点拓扑序DP,dp[x][i]表示x节点处取模后结果为i的方案数。

   当x节点处接受字符c可转移到y时,枚举i(0<=i<k),可以得到方程:dp[y][(i*10+c)%k] = sigma{ dp[x][i] | tans[x][c]=y }

#include <iostream>
#include <iomanip>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <limits>
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define x first
#define y second
#define sf scanf
#define pf printf
#define vec vector
#define pb push_back
#define mp make_pair
#define sz(a) int(a.size())
#define all(a) a.begin(),a.end()
#define clr(a,b) memset(a,b,sizeof(a))
#define bin_cnt(x) __builtin_popcount(x)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rrep(i,b,a) for(int i=b;i>=a;i--)
#define srep(sub,s) for(int sub=s&(s-1);sub;sub=(sub-1)&s)
#define irep(i,a) for(__typeof(a.begin()) i=a.begin();i!=a.end();i++)
#define irrep(i,a) for(__typeof(a.rbegin()) i=a.rbegin();i!=a.rend();i++)
#define inf numeric_limits<int>::max()
#define finf numeric_limits<double>::infinity()
#define eps 1e-8
#pragma GCC optimize ("O3")
#define o3 __attribute__((optimize("O3")))
#pragma comment(linker, "/STACK:32505856")

template<class T> inline T sqr(T a) {return a*a;}
template<class T> inline void gmin(T&a,T b) {if(a>b)a=b;}
template<class T> inline void gmax(T&a,T b) {if(a<b)a=b;}
inline int dcmp(const double &a) {return a>eps?1:(a<-eps?-1:0);}
struct Initializer{Initializer(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);}}initializer;

int K;
ll ans[32];

struct SAM {
  #define N 50005
  int tot,last;
  int go[N<<1][10],pre[N<<1],step[N<<1];
  int chr[N<<1],din[N<<1];
  ll dp[N<<1][32];

  inline void new_node(int s) {
    step[++tot]=s;
    pre[tot]=0;
    din[tot]=0;
    clr(go[tot],0);
    clr(dp[tot],0);
  }

  inline void build(char *s) {
    tot=0,last=1;
    new_node(0);
    chr[tot]=0;
    int n=strlen(s);
    rep(i,0,n-1) {
      new_node(step[last]+1);
      int c=s[i]-'0';
      chr[tot]=c;
      int p=last,np=tot,q,nq;
      for (;p&&!go[p][c];p=pre[p]) go[p][c]=np;
      if (!p) pre[np]=1;
        else {
          q=go[p][c];
          if (step[q]==step[p]+1) pre[np]=q;
            else {
              new_node(step[p]+1), nq=tot;
              chr[tot]=c;
              rep(j,0,9) go[nq][j]=go[q][j];
              pre[nq]=pre[q];
              pre[np]=pre[q]=nq;
              for (;p&&go[p][c]==q;p=pre[p]) go[p][c]=nq;
            }
        }
      last=np;
    }
  }

  void solve() {
    clr(ans,0);
    dp[1][0]=1;

    queue<int> q;
    rep(i,1,tot) rep(j,0,9) if (go[i][j]) din[go[i][j]]++;
    rep(i,1,tot) if (!din[i]) q.push(i);

    while (sz(q)) {
      int i=q.front();

      rep(j,0,9) if (go[i][j]) {
        if (!--din[go[i][j]]) {
          q.push(go[i][j]);
        }
        if (i==1 && j==0) continue;
        rep(k,0,K-1) {
          dp[go[i][j]][(k*10+j)%K] += dp[i][k];
        }
      }
      rep(k,0,K-1) ans[k]+=dp[i][k];

      q.pop();
    }
  }
} sam;


int main() {
  static char s[N];
  while (~sf("%s%d",s,&K)) {
    sam.build(s);
    sam.solve();
    ans[0]-=string(s).find('0')==string::npos;
    rep(i,0,K-1) pf("%lld%s",ans[i],i<K-1?" ":"\n");
  }
  return 0;
}

 

HDU5343 MZL's Circle Zhou

出处:2015 Multi-University Training Contest 5 - 1001

题意:依次给出长度90000以内的A、B两串,求将A的子串与B的子串按顺序拼接能得到的不重复的串的个数。

思路:A、B两串分别建两个SAM处理,先将B串翻转,利用SAM(B)求以字符c结尾(开头)的子串个数。

   对于SAM(A)中节点x,若x不能接受字符c,则将B串中以c开头的子串“接”在其后,构成Asub+Bsub的形式。

   详见官方题解 http://blog.sina.com.cn/s/blog_15139f1a10102vokk.html

#include <iostream>
#include <iomanip>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <bitset>
#include <limits>
using namespace std;
#define ll long long
#define ld long double
#define pii pair<int,int>
#define x first
#define y second
#define sf scanf
#define pf printf
#define vec vector
#define pb push_back
#define mp make_pair
#define sz(a) int(a.size())
#define all(a) a.begin(),a.end()
#define clr(a,b) memset(a,b,sizeof(a))
#define bin_cnt(x) __builtin_popcount(x)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rrep(i,b,a) for(int i=b;i>=a;i--)
#define srep(sub,s) for(int sub=s&(s-1);sub;sub=(sub-1)&s)
#define irep(i,a) for(__typeof(a.begin()) i=a.begin();i!=a.end();i++)
#define irrep(i,a) for(__typeof(a.rbegin()) i=a.rbegin();i!=a.rend();i++)
#define inf numeric_limits<int>::max()
#define finf numeric_limits<double>::infinity()
#define eps 1e-8
#pragma GCC optimize ("O3")
#define o3 __attribute__((optimize("O3")))
#pragma comment(linker, "/STACK:32505856")

template<class T> inline T sqr(T a) {return a*a;}
template<class T> inline void gmin(T&a,T b) {if(a>b)a=b;}
template<class T> inline void gmax(T&a,T b) {if(a<b)a=b;}
inline int dcmp(const double &a) {return a>eps?1:(a<-eps?-1:0);}
struct Initializer{Initializer(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);}}initializer;

struct SAM {
  #define N 90009
  int tot,last;
  int go[N<<1][26],pre[N<<1],step[N<<1];
  int chr[N<<1];

  inline void new_node(int s) {
    step[++tot]=s;
    pre[tot]=0;
    clr(go[tot],0);
  }

  inline void build(char *s) {
    tot=0,last=1;
    new_node(0);
    int n=strlen(s);
    rep(i,0,n-1) {
      new_node(step[last]+1);
      int c=s[i]-'a';
      chr[tot]=c;
      int p=last,np=tot,q,nq;
      for (;p&&!go[p][c];p=pre[p]) go[p][c]=np;
      if (!p) pre[np]=1;
        else {
          q=go[p][c];
          if (step[q]==step[p]+1) pre[np]=q;
            else {
              new_node(step[p]+1), nq=tot;
              chr[tot]=c;
              rep(j,0,25) go[nq][j]=go[q][j];
              pre[nq]=pre[q];
              pre[np]=pre[q]=nq;
              for (;p&&go[p][c]==q;p=pre[p]) go[p][c]=nq;
            }
        }
      last=np;
    }
  }
} sam;

#define ull unsigned long long
char a[N],b[N];
ull dp[26];

int main() {
  int T;
  sf("%d",&T);
  while (T--) {
    sf("%s%s",a,b);
    clr(dp,0);

    reverse(b,b+strlen(b));
    sam.build(b);
    rep(i,1,sam.tot) {
      dp[sam.chr[i]]+=sam.step[i]-sam.step[sam.pre[i]];
    }

    ull ans=0;
    sam.build(a);
    rep(i,1,sam.tot) {
      ans+=(ull)sam.step[i]-sam.step[sam.pre[i]];
      rep(c,0,25)
        if (!sam.go[i][c])
          ans+=(ull)(sam.step[i]-sam.step[sam.pre[i]])*dp[c];
    }
    cout<<ans+1<<endl;
  }
  return 0;
}

 

两道题均围绕不重复子串的数量展开询问,很容易联想到使用后缀自动机求解。

以前做过较长时间的SAM专题并进行过总结,但时间久远差不多都忘了,正好借机重温一下。

posted @ 2015-08-05 16:44  ALPC_Flagship  阅读(354)  评论(0编辑  收藏  举报