zoukankan      html  css  js  c++  java
  • BZOJ2754: [SCOI2012]喵星球上的点名

    BZOJ2754: [SCOI2012]喵星球上的点名

    Description

    a180285幸运地被选做了地球到喵星球的留学生。
    他发现喵星人在上课前的点名现象非常有趣。  
    假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成。
    喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。 
    然而,由于喵星人的字码过于古怪,以至于不能用ASCII码来表示。
    为了方便描述,a180285决定用数串来表示喵星人的名字。
    现在你能帮助a180285统计每次点名的时候有多少喵星人答到,以及M次点名结束后每个喵星人答到多少次吗?  

    Input

    现在定义喵星球上的字符串给定方法:
    先给出一个正整数L,表示字符串的长度,接下来L个整数表示字符串的每个字符。
    输入的第一行是两个整数N和M。
    接下来有N行,每行包含第i 个喵星人的姓和名两个串。姓和名都是标准的喵星球上的字符串。
    接下来有M行,每行包含一个喵星球上的字符串,表示老师点名的串。

    Output

    对于每个老师点名的串输出有多少个喵星人应该答到。
    然后在最后一行输出每个喵星人被点到多少次。

    Sample Input

    2 3
    6 8 25 0 24 14 8 6 18 0 10 20 24 0
    7 14 17 8 7 0 17 0 5 8 25 0 24 0
    4 8 25 0 24
    4 7 0 17 0
    4 17 0 8 25

    Sample Output

    2
    1
    0
    1 2
    【提示】
    事实上样例给出的数据如果翻译成地球上的语言可以这样来看
    2 3
    izayoi sakuya
    orihara izaya
    izay
    hara
    raiz

    HINT

    【数据范围】 
     对于30%的数据,保证: 
    1<=N,M<=1000,喵星人的名字总长不超过4000,点名串的总长不超过2000。
    对于100%的数据,保证:
    1<=N<=20000,1<=M<=50000,喵星人的名字总长和点名串的总长分别不超过100000,保证喵星人的字符串中作为字符存在的数不超过10000。


    题解Here!
    先来一发$AC$自动机的暴力,复杂度?$O( ext{能过})$。。。

    我当然是开心的写了$AC$自动机 + 暴力转移。。。

    每次沿着$fail$指针跳,然后计算, 注意判断是否计算过。

    $BUT$!要用$map$存$Trie$树的儿子节点!不然?你说呢。。。

    其实我也不想用$STL$,但是出题人太毒了我也没有辨法。。。

    附代码:
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<map>
    #include<vector>
    #define MAXN 100010
    using namespace std;
    int n,m;
    int ans_one[MAXN],ans_two[MAXN];
    vector<int> last_name[MAXN],first_name[MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    namespace AC{
        int size=0;
        bool vis[MAXN],mark[MAXN];
        vector<int> V,M;
        struct AC_Automaton{
            int fail;
            vector<int> val;
            map<int,int> son;
            AC_Automaton(){fail=0;}
        }a[MAXN];
        void insert(int v){
            int u=0,l=read();
            for(int i=1;i<=l;i++){
                int name=read();
                if(!a[u].son[name])a[u].son[name]=++size;
                u=a[u].son[name];
            }
            a[u].val.push_back(v);
        }
        void buildtree(){
            int u,v;
            queue<int> q;
            for(map<int,int>::iterator i=a[0].son.begin();i!=a[0].son.end();i++){
                a[i->second].fail=0;
                q.push(i->second);
            }
            while(!q.empty()){
                u=q.front();
                q.pop();
                for(map<int,int>::iterator i=a[u].son.begin();i!=a[u].son.end();i++){
                    int k=a[u].fail,now=i->first;
                    while(k&&!a[k].son[now])k=a[k].fail;
                    a[i->second].fail=a[k].son[now];
                    q.push(i->second);
                }
            }
        }
        void calculate(int x,int u){
            for(int i=u;i;i=a[i].fail){
                if(!vis[i]){
                    vis[i]=true;
                    V.push_back(i);
                    for(int j=0;j<a[i].val.size();j++)
                    if(!mark[a[i].val[j]]){
                        mark[a[i].val[j]]=true;
                        M.push_back(a[i].val[j]);
                        ans_one[a[i].val[j]]++;
                        ans_two[x]++;
                    }
                }
                else return;
            }
        }
        void solve(int x){
            int u=0;
            for(int i=0;i<last_name[x].size();i++){
                int c=last_name[x][i];
                while(u&&!a[u].son[c])u=a[u].fail;
                u=a[u].son[c];
                calculate(x,u);
            }
            u=0;
            for(int i=0;i<first_name[x].size();i++){
                int c=first_name[x][i];
                while(u&&!a[u].son[c])u=a[u].fail;
                u=a[u].son[c];
                calculate(x,u);
            }
            for(int i=0;i<V.size();i++)vis[V[i]]=false;
            for(int i=0;i<M.size();i++)mark[M[i]]=false;
            V.clear();M.clear();
        }
    }
    void work(){
        for(int i=1;i<=n;i++)AC::solve(i);
        for(int i=1;i<=m;i++)printf("%d
    ",ans_one[i]);
        for(int i=1;i<=n;i++)printf("%d ",ans_two[i]);
        printf("
    ");
    }
    void init(){
        int x,y;
        n=read();m=read();
        for(int i=1;i<=n;i++){
            x=read();
            for(int j=1;j<=x;j++){
                y=read();
                last_name[i].push_back(y);
            }
            x=read();
            for(int j=1;j<=x;j++){
                y=read();
                first_name[i].push_back(y);
            }
        }
        for(int i=1;i<=m;i++)AC::insert(i);
        AC::buildtree();
    }
    int main(){
        init();
        work();
        return 0;
    }
    

    然后是正解。


    正解:

    离线+后缀数组+树状数组。

    把所有串串起来建后缀数组,对于每个询问串首向左向右二分找合法区间。

    问题就转化成求每个区间包含多少种颜色,和每种颜色被多少区间包含。

    都可以用树状数组做。

    第一问,有个类似的题:BZOJ1878: [SDOI2009]HH的项链

    直接跑树状数组。

    第二问跟第一问类似,都是对序列遍历一遍,在区间的左右端点$L,R$,以及当前点和与当前点同色的上一个点$front[i],i$的操作.

    第二问是访问到$L$处给树状数组$bit[L]++$,到$R$时$bit[L]--$,到i时查询$sum(i)-sum(front[i])$, 和第一问相反。

    复杂度是严格$O(nlog_2n)$的。

    但是这个方法常数有点大,我也不知道我哪里写挫了。。。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #define MAXN 501010
    #define MAX 999999999
    using namespace std;
    int n,m,q;
    int val[MAXN],id[MAXN],colour[MAXN],num[MAXN],bit_one[MAXN],bit_two[MAXN];
    int front[MAXN],pos[MAXN],lside[MAXN],lg[MAXN],ans_one[MAXN],ans_two[MAXN];
    int size,sa[MAXN],rk[MAXN],tax[MAXN],tp[MAXN],height[MAXN],f[MAXN][20];
    struct Question{
        int l,r,id;
    }que[MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9')c=getchar();
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    bool cmp(const Question &p,const Question &q){
        return p.r<q.r;
    }
    void radixsort(){
        for(int i=0;i<=size;i++)tax[i]=0;
        for(int i=1;i<=n;i++)tax[rk[i]]++;
        for(int i=1;i<=size;i++)tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--)sa[tax[rk[tp[i]]]--]=tp[i];
    }
    void suffixsort(int x){
        size=x+1;
        for(int i=1;i<=n;i++){
            rk[i]=val[i];
            tp[i]=i;
        }
        radixsort();
        for(int w=1,p=0;p<n;size=p,w<<=1){
            p=0;
            for(int i=1;i<=w;i++)tp[++p]=n-w+i;
            for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
            radixsort();
            swap(tp,rk);
            rk[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
            rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
        }
    }
    void getheight(){
        for(int i=1,j,k=0;i<=n;i++){
            if(k)k--;
            j=sa[rk[i]-1];
            while(val[i+k]==val[j+k])k++;
            height[rk[i]]=k;
        }
    }
    void step(){
        for(int i=1;i<=n;i++)f[i][0]=height[i];
        for(int i=1;(1<<i)<=n;i++)
        for(int j=1;j+(1<<i)-1<=n;j++)
        f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]);
        for(int i=1;(1<<i)<=n;++i)lg[1<<i]=i;
        for(int i=1;i<=n;++i)if(!lg[i])lg[i]=lg[i-1];
    }
    int query(int l,int r){
        if(l==r)return MAX;
        if(l>r)swap(l,r);
        int k=lg[r-(l++)];
        return min(f[l][k],f[r-(1<<k)+1][k]);
    }
    inline int lowbit(int x){return x&(-x);}
    inline void update_one(int x,int v){if(!x)return;for(;x<=n;x+=lowbit(x))bit_one[x]+=v;}
    inline void update_two(int x,int v){if(!x)return;for(;x<=n;x+=lowbit(x))bit_two[x]+=v;}
    inline int sum_one(int x){int s=0;for(;x;x-=lowbit(x))s+=bit_one[x];return s;}
    inline int sum_two(int x){int s=0;for(;x;x-=lowbit(x))s+=bit_two[x];return s;}
    void work(){
        for(int i=1,j=1,k=1;i<=n;i++){
            for(;j<=q&&lside[j]==i;j++)update_two(i,1);
            if(colour[sa[i]]>0){
                ans_two[colour[sa[i]]]+=sum_two(i)-sum_two(front[i]);
                update_one(i,1);update_one(front[i],-1);
            }
            for(;k<=q&&que[k].r==i;k++){
                ans_one[que[k].id]=sum_one(que[k].r)-sum_one(que[k].l-1);
                update_two(que[k].l,-1);
            }
        }
        for(int i=1;i<=q;i++)printf("%d
    ",ans_one[i]);
        for(int i=1;i<=m;i++)printf("%d ",ans_two[i]);
        printf("
    ");
    }
    void init(){
        int x,w=10001;
        n=0;
        m=read();q=read();
        for(int i=1;i<=m;i++)
        for(int j=1;j<=2;j++){
            x=read();
            while(x--){
                colour[++n]=i;
                val[n]=read();
            }
            val[++n]=w;
        }
        for(int i=1;i<=q;i++){
            x=num[n+1]=read();
            id[n+1]=i;
            while(x--){
                colour[++n]=-i;
                val[n]=read();
            }
            val[++n]=w;
        }
        suffixsort(w);
        getheight();
        step();
        for(int i=1;i<=n;i++){
            if(colour[sa[i]]>0){
                front[i]=pos[colour[sa[i]]];
                pos[colour[sa[i]]]=i;
            }
            if(id[i]){
                que[id[i]].id=id[i];
                int l=1,r=rk[i];
                while(l<r){
                    int mid=l+r>>1;
                    if(query(mid,rk[i])>=num[i])r=mid;
                    else l=mid+1;
                }
                que[id[i]].l=lside[id[i]]=l;
                l=rk[i];r=n;
                while(l<r){
                    int mid=l+r+1>>1;
                    if(query(rk[i],mid)>=num[i])l=mid;
                    else r=mid-1;
                }
                que[id[i]].r=r;
            }
        }
        sort(que+1,que+q+1,cmp);
        sort(lside+1,lside+q+1);
    }
    int main(){
        init();
        work();
        return 0;
    }

    还有一种更快的正解:后缀自动机

    $SAM$是吊打$SA$,$AC$自动机的一种数据结构。

    (坑,未填。。。)

  • 相关阅读:
    .NET Framework 精简版多线程提示
    创建全球化的 Windows Mobile 应用程序
    【转】Windows Mobile 进阶系列——多窗体应用的性能与编程调试1
    关于MOBILE注册表操作.
    windows下squid安装与配置
    关于Windows mobile注册表
    aaaaaaaaaaaaaa
    记GraphicsMagick压缩图片命令
    使用Sublime Text 2开发php
    SQL Server 2005中使用事务发布实现数据库复制
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9431760.html
Copyright © 2011-2022 走看看