bzoj2754[SCOI2012]喵星球上的点名

这道题的输出格式好萌好喵啊,竟然是竖着一列然后横着再拐过去的。

题意:给出n组字符串,每组两个字符串A和B(“姓”和“名”),给出m个询问,每个询问包含一个串C,对于某一组字符串,如果C是A或者B的子串,那么称C在这组字符串中出现.对于每个询问串,输出它在多少组字符串中出现.对于每组字符串,输出有多少个询问串在这一组字符串中出现.n<=20000,m<=50000,询问串总长<=100000,n组字符串总长<=100000,字符集大小10000,

分析:这个题看上去很像模式匹配问题,又是多串,所以考虑AC自动机.

先处理每个询问串在多少组中出现.先考虑AC自动机可以处理的较简单的问题.

如果对每个询问串只统计总共出现了多少次(在同一个串中多次出现算作多次,没有“一组”的限制),AC自动机可以直接做.

如果统计每个询问串在多少个串中出现,(在同一个串中多次出现算作一次但不考虑一组的限制,在同一组的两个串中出现算作两次)(也就是bzoj2780 sevenk loves oimaster或bzoj3881Divljak),AC自动机+树链的并,利用树状数组和fail树的DFS序可以做.

现在如果某个询问串在一组中的两个串里都出现了,应当只算一次,那么我们把这一组的两个串在AC自动机上经过的节点一起做树链的并即可.建议先做bzoj2780和bzoj3881.这道题的操作并不是在线的,应该可以使用树上差分打打标记,不过树状数组也很好写并且复杂度足够.

第二问的处理方法和第一问相同,也是树链的并,我们预处理fail树上每个节点x到根节点路径上单词节点的个数sum[x],然后用树链的并求和:首先将一组的两个串扔进AC自动机跑,把经过的节点拿出来按fail树的dfs序排序,首先把每个经过的节点的sum值加起来,这时的答案是有重复计数的,然后对dfs序相邻的两个节点,把答案减去它们的lca的sum值.

这两问的处理方法其实具有对称性,一个进行求和,一个进行修改.

因为字符集过大,AC自动机上节点的儿子数组需要用map,当然也不能补全为trie图.AC自动机匹配的时候会需要沿着fail指针往回跳,但复杂度还是线性的.(我一开始忘了,以为不补成trie图的复杂度是错的....)

粘一些链接:

对解法的一些总结http://www.tuicool.com/articles/zYrmM3Z 

这个小哥写了树链的并,然后又写HH的项链,并不是很懂.http://blog.csdn.net/clover_hxy/article/details/52502544 

远古神犇Vani的神代码https://github.com/Azure-Vani/acm-icpc/blob/master/bzoj/2754.cpp

#include<cstdio>
#include<cassert>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
const int maxn=50005;
struct node{
  map<int,node*> ch;
  node* fail;int num,val;
  node(int x){
    ch.clear();fail=0;num=x;val=0;
  }
}*root;int tot=1;
int str[maxn];
int len1[maxn],len2[maxn],lenq[maxn];
vector<int> name1[maxn],name2[maxn],query[maxn];
node* pos[100005];int pos2[100005];
void Add(vector<int> str,int len,int x){
  node* p=root;
  for(int i=0;i<len;++i){
    if(p->ch[str[i]]==NULL){
      p->ch[str[i]]=new node(++tot);pos[tot]=p->ch[str[i]];
    }
    p=p->ch[str[i]];
  }
  p->val++;pos2[x]=p->num;
}
struct edge{
  int to,next;
}lst[100005];int len=1,first[100005];
void addedge(int a,int b){//printf("%d %d\n",a,b);
  lst[len].to=b;lst[len].next=first[a];first[a]=len++;
}
void getfail(){
  queue<node*> q;q.push(root);
  while(!q.empty()){
    node* x=q.front();q.pop();
    for(map<int,node*>::iterator pt=x->ch.begin();pt!=x->ch.end();++pt){
      if(x==root)pt->second->fail=root;
      else{
    node* p=x->fail;int t=pt->first;
    while(p!=root&&p->ch[t]==NULL)p=p->fail;
    if(p->ch[t])pt->second->fail=p->ch[t];
    else pt->second->fail=root;
      }
      addedge(pt->second->fail->num,pt->second->num);
      q.push(pt->second);
    }
  }
}
int ans1[maxn],ans2[maxn];
int seq[100005],cnt=0;
int dfn[100005],T,p[100005][20],depth[100005],sum[100005],sz[100005];
void dfs(int x){
  dfn[x]=++T;
  for(int j=0;p[x][j];++j)p[x][j+1]=p[p[x][j]][j];
  sz[x]=1;
  for(int pt=first[x];pt;pt=lst[pt].next){
    p[lst[pt].to][0]=x;depth[lst[pt].to]=depth[x]+1;
    sum[lst[pt].to]=sum[x]+pos[lst[pt].to]->val;
    dfs(lst[pt].to);sz[x]+=sz[lst[pt].to];
  }
}
int lca(int u,int v){
  if(depth[u]<depth[v])swap(u,v);
  for(int j=19;j>=0;--j){
    if(depth[p[u][j]]>=depth[v])u=p[u][j];
  }
  if(u==v)return u;
  for(int j=19;j>=0;--j)if(p[u][j]!=p[v][j])u=p[u][j],v=p[v][j];
  return p[v][0];
}
void go(vector<int> str,int n){
  node* p=root;
  for(int i=0;i<n;++i){
    int t=str[i];
    while(p!=root&&p->ch[t]==NULL)p=p->fail;
    if(p->ch[t])p=p->ch[t];
    if(p!=root)seq[cnt++]=p->num;
  }
}
bool cmp(const int &a,const int &b){
  return dfn[a]<dfn[b];
}
int bit[100005];
void add(int x,int w){
  for(;x<100005;x+=x&(-x))bit[x]+=w;
}
int pre(int x){
  int ans=0;for(;x;x-=x&(-x))ans+=bit[x];return ans;
}
int calc(int l,int r){
  return pre(r)-pre(l-1);
}
void process(int x){
  cnt=0;
  go(name1[x],len1[x]);go(name2[x],len2[x]);//printf("%d\n",cnt);
  sort(seq,seq+cnt,cmp);
  for(int i=0;i<cnt;++i){
    add(dfn[seq[i]],1);ans2[x]+=sum[seq[i]];
  }
  int LCA;
  for(int i=1;i<cnt;++i){
    LCA=lca(seq[i],seq[i-1]);
    add(dfn[LCA],-1);ans2[x]-=sum[LCA];
  }
}
int main(){
  int n,m,x;scanf("%d%d",&n,&m);
  root=new node(1);pos[1]=root;
  for(int i=1;i<=n;++i){
    scanf("%d",&len1[i]);
    for(int j=0;j<len1[i];++j){
      scanf("%d",&x);name1[i].push_back(x);
    }
    scanf("%d",&len2[i]);
    for(int j=0;j<len2[i];++j){
      scanf("%d",&x);name2[i].push_back(x);
    }
  }
  for(int i=1;i<=m;++i){
    scanf("%d",&lenq[i]);
    for(int j=0;j<lenq[i];++j){
      scanf("%d",&x);query[i].push_back(x);
    }
    Add(query[i],lenq[i],i);
  }
  getfail();
  dfs(1);
  for(int i=1;i<=n;++i){
    process(i);
  }
  for(int i=1;i<=m;++i){
    int x=pos2[i];
    ans1[i]=calc(dfn[x],dfn[x]+sz[x]-1);
  }
  for(int i=1;i<=m;++i)printf("%d\n",ans1[i]);
  for(int i=1;i<=n;++i)printf("%d%c",ans2[i],(i==n)?'\n':' ');
  return 0;
}

 

posted @ 2017-03-14 18:49  liu_runda  阅读(1136)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难