「SCOI2012」喵星球上的点名

「SCOI2012」喵星球上的点名

填一个很久以前用 \(\texttt{AC}\) 自动机没填上的坑。

关于本题,能够通过本题的算法很多,这里作者采用的是后缀数组+树状数组的做法。

首先有一个显然的结论:若 \(s_2\) 是 \(s_1\) 的子串,则 \(s_1\) 一定存在一个后缀与 \(s_2\) 的最长公共前缀为 \(|s_2|\)。

我们将读入的姓、名、询问串连成一个整体,形成一个字符串 \(s\),且在每一个姓、名、询问串中插入一个不存在文本中的字符,且保证询问串后插入的比姓名串后插入的字符字典序小

然后我们求出字符串 \(s\) 的 \(\texttt{height}\) 数组,将两后缀的 \(\texttt{LCP}\) 转化为 \(\texttt{RMQ}\) 问题。

根据上面我们的构造方式可以得到:如果一个询问可以被响应,那么\(\texttt{sa}\) 数组中一定存在下标连续的一段,它们所代表的后缀的 \(\texttt{LCP}\) 等于询问串长度,且这一段的开头一定是以这个询问串开头的后缀。

原因也很简单,因为我们在询问串后插入的分割字符是比插入在姓名串后的字符小的,所以按照后缀大小排序后自然排在相对靠前的位置。

这启发我们可以通过二分查找右端点(左端点已经确定,就是询问串开头的后缀),将每个询问转化成一个区间,然后问题转化为:

  • 每个区间内不同归属的姓名串个数。
  • 每个姓名串被多少个区间覆盖。

问题一是一个经典问题,即区间内有多少种不同的颜色。可以用离线后用树状数组完成,这里给出一种具体做法(不唯一):将每个询问固定至其右端点上,从小到大枚举右端点,在树状数组中将每种颜色最靠近右端点(不超过右端点)的位置加 \(1\) 统计,这样可以保证最优。所以我们需要维护一个 \(\texttt{pre}\) 数组,表示当前位置颜色上一次出现的位置。

问题二在我们维护了 \(\texttt{pre}\) 数组过后也变得较为简单。首先将每个区间在树状数组上加 \(1\)。仍然将每个区间固定至其右端点,枚举右端点,计算每个点的贡献,即 \(sum(i)-sum(pre[i])\),然后将以当前位置为右端点的区间在树状数组上减 \(1\)。

至此,本题可以在 \(O(|s|log_2|s|)\) 的复杂度内解决。

贴代码(人傻自带大常数)

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+5;
int s[maxn];
int sa[maxn],rk[maxn],tp[maxn],cnt[maxn];
int nn,n,m,q;
int height[maxn],h[maxn];
int col[maxn],len[maxn],vis[maxn];
int mn[maxn][21],lg[maxn];
int tree[maxn];
int L[maxn],R[maxn],tot;
int lst[maxn],pre[maxn];
int ans1[maxn],ans2[maxn];
vector<pair<int,int> > v[maxn];
vector<int>v2[maxn];
inline void add(int x,int k){
    for(;x<=n&&x;x+=x&-x) tree[x]+=k;
}
inline int sum(int x){
    int ans=0;
    for(;x;x-=x&-x) ans+=tree[x];
    return ans;
}
inline void sort(){

    memset(cnt,0,sizeof (int)*(m+1));
    for(int i=1;i<=n;++i) ++cnt[rk[i]];
    for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1];
    for(int i=n;i>=1;--i) sa[cnt[rk[tp[i]]]--]=tp[i];
}
inline void init(){
    lg[0]=-1;
    for(int i=1;i<=n;++i) mn[i][0]=height[i],lg[i]=lg[i>>1]+1;
    for(int j=1;j<=log2(n);++j)
        for(int i=1;i+(1<<j)-1<=n;++i)
            mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]);
}
inline int rmq(int l,int r){
    int s=lg[r-l+1];
    return min(mn[l][s],mn[r-(1<<s)+1][s]);
}
inline void SA(){
    m=220000;
    for(int i=1;i<=n;++i) rk[i]=s[i]+1,tp[i]=i;
    sort();
    for(int w=1,p=0;p<n;m=p,w<<=1){
        p=0;
        for(int i=n;i>n-w;--i) tp[++p]=i;
        for(int i=1;i<=n;++i) if(sa[i]>w) tp[++p]=sa[i]-w;
        sort();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;
    }
    for(int i=1;i<=n;++i) rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=n;++i){
        if(rk[i]==1) continue;
        if(k) --k;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) ++k;
        height[rk[i]]=k;
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>nn>>q;
    for(int i=1;i<=nn;++i){
        int opt;cin>>opt;
        for(int j=1;j<=opt;++j) col[++n]=i,cin>>s[n],s[n]+=nn+q;
        s[++n]=-i+nn+q;
        cin>>opt;
        for(int j=1;j<=opt;++j) col[++n]=i,cin>>s[n],s[n]+=nn+q;
        s[++n]=-i+nn+q;
    }
    for(int i=1;i<=q;++i){
        cin>>len[i];vis[n+1]=i;
        for(int j=len[i];j;--j){
            cin>>s[++n],s[n]+=nn+q;
        }
        s[++n]=-i+q;
    }
    SA();
    init();
    for(int i=1;i<=n;++i){
        if(vis[sa[i]]){
            int l=i+1,r=n;
            int ans=i;
            while(l<=r){
                int mid=(l+r)>>1;
                if(rmq(i+1,mid)>=len[vis[sa[i]]]){
                    ans=max(ans,mid),l=mid+1;
                }
                else r=mid-1;
            }
            v[ans].emplace_back(i,vis[sa[i]]);//二分求右焦点
            L[++tot]=i,R[tot]=ans+1;//这里保存的区间是[l,r+1],所以后面就不用+1了
        }
        if(col[sa[i]]){
            pre[i]=lst[col[sa[i]]];
            lst[col[sa[i]]]=i;
        }
    }
    for(int i=1;i<=n;++i){
        if(col[sa[i]]){
            add(pre[i],-1),add(i,1);
        }
        for(auto x:v[i])
            ans1[x.second]=sum(i)-sum(x.first-1);
    }//问题一
    memset(tree,0,sizeof tree);
    for(int i=1;i<=tot;++i) add(L[i],1),add(R[i],-1),v2[R[i]].emplace_back(L[i]);
    for(int i=1;i<=n;++i){
        for(auto x:v2[i])
            add(x,-1),add(i,1);
        ans2[col[sa[i]]]+=sum(i)-sum(pre[i]);
    }//问题二
    for(int i=1;i<=q;++i) cout<<ans1[i]<<'\n';
    for(int i=1;i<=nn;++i) cout<<ans2[i]<<' ';
    return 0;

原文地址:https://www.cnblogs.com/HenryHuang-Never-Settle/p/solution-P2336.html

时间: 2024-08-29 07:25:53

「SCOI2012」喵星球上的点名的相关文章

BZOJ_2754__[SCOI2012]_喵星球上的点名_(暴力+后缀数组)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=2754 给出n个姓名串和m个点名串.求每个点名串在多少人的姓名中出现过(在名中出现或在姓中出现,不能跨越),以及最后每个人被点到多少次. 分析 这种解法是用后缀数组优化一下暴力,(优化了吗?)复杂度并不能保证,然而能A... 我们先把所有名,姓,点名串都接在一个串里面. 然后匹配问题就转化成了后缀的最长公共前缀问题. 但是由于我们讨论问题的对象是名,姓,以及点名串,所以每个部分我们用不同的分隔

bzoj2754【SCOI2012】喵星球上的点名

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 1577  Solved: 710 [Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣.   假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串

BZOJ 2754 【SCOI2012】 喵星球上的点名

题目链接:喵星球上的点名 首先可以发现姓和名两个串就是逗你玩的.在两个串中间插入一个\(10001\),当成一个串做就可以了. 于是我们的问题转化为了: 有\(n\)个串\(A_1,A_2,\dots,A_n\)和\(m\)个串\(B_1,B_2,\dots,B_m\),要对于每个\(B_i\)求出它被多少个\(A\)串包含,并要对每个\(A_i\)求出它包含了多少个\(B\)串. 我们先把所有串丢到一个\(AC\)自动机里面,然后构出\(fail\)树.我们知道,如果\(S\)串包含了\(A\

【BZOJ2754】【SCOI2012】喵星球上的点名 后缀数组优化暴力

转载请注明出处谢谢:http://blog.csdn.net/vmurder/article/details/42963375 题意: 那个输入中每个串先是一个长度然后才是串. 然后如果某猫姓名abcd·efgh,那么点名abc,bcd,fg等都是好使的,但是cde就不行. 然后输入姓名时格式为一行 a a个数,b b个数. A表示姓,B表示名. 题解: 直接暴力枚举每个点名是哪些的子串, 然后我们发现可以用后缀数组来优化这个事情~~ 时间复杂度是不准确的,也就是说可以被卡成TLE,但是大家都没

BZOJ 2754: [SCOI2012]喵星球上的点名 [后缀数组+暴力]

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1906  Solved: 839[Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣.   假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那

BZOJ 2754([SCOI2012]喵星球上的点名-后缀数组统计序列集合中子序列出现次数)

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MB Submit: 805  Solved: 380 [Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣.   假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,

BZOJ 2754: [SCOI2012]喵星球上的点名

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1926  Solved: 850[Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这

bzoj 2754 [SCOI2012]喵星球上的点名(后缀数组)

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 1359  Solved: 618[Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣.   假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那

bzoj2754 [SCOI2012]喵星球上的点名 (后缀数组+树状数组)

2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MB Submit: 2745 Solved: 1190 [Submit][Status][Discuss] Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么