P2336 [SCOI2012]喵星球上的点名(SA+莫队)
一道还算有点含金量的 SA 罢……
首先按照套路我们把读入的所有字符串都粘在一起,中间用分隔符隔开并建出后缀数组出来。
我们考虑对于一个固定的字符串 \(s\),什么样的字符串 \(t\) 满足 \(s\) 为 \(t\) 的子串:如果存在 \(t\) 的某个后缀 \(t'\) 使得 \(\text{LCP}(t',s)=|s|\) 那么 \(s\) 就是 \(t\) 的子串。
故我们可以想到一个很暴力的做法,枚举每个喵星人的姓/名的每一个后缀 \(suf\),如果 \(\text{LCP}(suf,s)=|s|\) 就意味着 \(s\) 在当前字符串中出现过了。
而根据 LCP Lemma,满足 \(\text{LCP}(s,t)\geq |s|\) 的 \(t\) 在后缀数组上显然对应一段区间 \([l,r]\)——这段区间可以通过二分+RMinQ 在 \(\mathcal O(n\log n)\) 的时间内求出。这个区间中每一个后缀与 \(s\) 的 LCP 长度都是 \(|s|\),也就意味着对于 \([l,r]\) 中每一个后缀对应的喵星人,它在此次点名中都会回答到。
于是此题转化为一个区间数颜色的问题——这是一个非常经典的问题,可以用莫队在 \(m\sqrt{n}\) 的时间内解决。
可是此题还有一个第二问,要求每个喵星人回答了多少次到。这其实也是一个非常套路的问题,考虑将这 \(m\) 次询问看作一个时间轴,我们将一个喵星人回答到的时间拆成一个个区间 \([l_1,r_1]\cup[l_2,r_2]\cup[l_3,r_3]\cup\dots\cup[l_m,r_m]\),那么显然这个喵星人会在时刻 \(l_i\) 被压入莫队(出现次数由 \(0\) 变为非零),在时刻 \(r_i+1\) 被弹出莫队(出现次数由非零变为 \(0\))。我们考虑利用差分的思想,当我们在 \(t\) 时刻压入一个元素时我们将其出现次数加上 \(m-t\),弹出一个元素时我们将其出现次数减去 \(m-t\),这样就能保证最终每个元素的出现次数就是所有 \(r_i-l_i+1\) 的和了。正确性显然。
时间复杂度 \(m\sqrt{n}\)(u1s1 这个数据范围有点迷惑人啊,一开始还以为莫队过不了呢)
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=5e4;
const int MAXM=1e5;
const int MAXL=4e5;
const int LOG_N=19;
int n,m,l=0,a[MAXL+5],bel[MAXL+5],sep_id=10001;pii x[MAXL+5];
int buc[MAXL+5],seq[MAXL+5],rk[MAXL+5],sa[MAXL+5],ht[MAXL+5];
int mn[MAXL+5][LOG_N+2],bg[MAXM+5],len[MAXM+5];
void getsa(){
int vmax=sep_id,gr=0;
for(int i=1;i<=l;i++) buc[a[i]]++;
for(int i=1;i<=vmax;i++) buc[i]+=buc[i-1];
for(int i=l;i;i--) sa[buc[a[i]]--]=i;
for(int i=1;i<=l;i++){
if(a[sa[i]]!=a[sa[i-1]]) gr++;
rk[sa[i]]=gr;
} vmax=gr;
for(int k=1;k<=l;k<<=1){
for(int i=1;i<=l;i++){
if(i+k<=l) x[i]=mp(rk[i],rk[i+k]);
else x[i]=mp(rk[i],0);
} memset(buc,0,sizeof(buc));gr=0;int num=0;
for(int i=l-k+1;i<=l;i++) seq[++num]=i;
for(int i=1;i<=l;i++) if(sa[i]>k) seq[++num]=sa[i]-k;
for(int i=1;i<=l;i++) buc[x[i].fi]++;
for(int i=1;i<=vmax;i++) buc[i]+=buc[i-1];
for(int i=l;i;i--) sa[buc[x[seq[i]].fi]--]=seq[i];
for(int i=1;i<=l;i++){
if(x[sa[i]]!=x[sa[i-1]]) gr++;
rk[sa[i]]=gr;
} vmax=gr;if(vmax==l) break;
}
}
void getht(){
int k=0;
for(int i=1;i<=l;i++){
if(rk[i]==1) continue;if(k) --k;
int j=sa[rk[i]-1];
while(i+k<=l&&j+k<=l&&a[i+k]==a[j+k]) k++;
ht[rk[i]]=k;
}
// for(int i=1;i<=l;i++) printf("%d\n",ht[i]);
}
int query(int x,int y){
int k=log2(y-x+1);
return min(mn[x][k],mn[y-(1<<k)+1][k]);
}
int getlcp(int x,int y){
if(x==y) return l-sa[x]+1;
if(x>y) swap(x,y);
return query(x+1,y);
}
int blk_sz,blk_cnt,lb[MAXL+5],rb[MAXL+5],bbel[MAXL+5];
struct interval{
int l,r,id;
bool operator <(const interval &rhs){
if(bbel[l]!=bbel[rhs.l]) return bbel[l]<bbel[rhs.l];
return r<rhs.r;
}
} q[MAXM+5];
int cnt[MAXN+5],num=0;
int ans[MAXM+5],ret[MAXN+5];
void push(int x,int t){
if(!x) return;
if(!cnt[x]) num++,ret[x]+=m-t+1;
cnt[x]++;
}
void pop(int x,int t){
if(!x) return;
cnt[x]--;
if(!cnt[x]) num--,ret[x]-=m-t+1;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,l1,l2;i<=n;i++){
scanf("%d",&l1);
for(int j=1,x;j<=l1;j++){scanf("%d",&x);a[++l]=++x;bel[l]=i;}
a[++l]=++sep_id;scanf("%d",&l2);
for(int j=1,x;j<=l2;j++){scanf("%d",&x);a[++l]=++x;bel[l]=i;}
a[++l]=++sep_id;
}
for(int i=1;i<=m;i++){
scanf("%d",&len[i]);bg[i]=l+1;
for(int j=1,x;j<=len[i];j++){scanf("%d",&x);a[++l]=++x;}
a[++l]=++sep_id;
} getsa();getht();
for(int i=1;i<=l;i++) mn[i][0]=ht[i];
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=l;j++)
mn[j][i]=min(mn[j][i-1],mn[j+(1<<i-1)][i-1]);
for(int i=1;i<=m;i++){
int L=1,R=rk[bg[i]],p=rk[bg[i]];
while(L<=R){
int mid=(L+R)>>1;
if(getlcp(mid,rk[bg[i]])>=len[i]) p=mid,R=mid-1;
else L=mid+1;
} q[i].l=p;
L=rk[bg[i]],R=l,p=rk[bg[i]];
while(L<=R){
int mid=(L+R)>>1;
if(getlcp(mid,rk[bg[i]])>=len[i]) p=mid,L=mid+1;
else R=mid-1;
} q[i].r=p;q[i].id=i;
// printf("%d %d\n",q[i].l,q[i].r);
}
blk_sz=(int)pow(l,0.5);blk_cnt=(l-1)/blk_sz+1;
for(int i=1;i<=blk_cnt;i++){
lb[i]=(i-1)*blk_sz+1;
rb[i]=min(i*blk_sz,l);
for(int j=lb[i];j<=rb[i];j++) bbel[j]=i;
} sort(q+1,q+m+1);
int cl=1,cr=0;
for(int i=1;i<=m;i++){//first push then pop
while(cr<q[i].r) push(bel[sa[++cr]],i);
while(cl>q[i].l) push(bel[sa[--cl]],i);
while(cr>q[i].r) pop(bel[sa[cr--]],i);
while(cl<q[i].l) pop(bel[sa[cl++]],i);
ans[q[i].id]=num;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
for(int i=1;i<=n;i++) printf("%d%c",ret[i],(i==n)?'\n':' ');
return 0;
}
P2336 [SCOI2012]喵星球上的点名(SA+莫队)的更多相关文章
- BZOJ2754 [SCOI2012]喵星球上的点名 SA+莫队+树状数组
题面 戳这里 题解 首先先把所有给出的姓名和询问全部接在一起,建出\(height\)数组. 某个串要包含整个询问串,其实就相当于某个串与询问串的\(lcp\)为询问串的长度. 而两个后缀\(Suff ...
- 洛咕 P2336 [SCOI2012]喵星球上的点名
洛咕 P2336 [SCOI2012]喵星球上的点名 先求出SA和height,一个点名串对应的就是一段区间,还有很多个点,就转化成了 有很多个区间,很多个点集,对每个区间计算和多少个点集有交,对每个 ...
- P2336 [SCOI2012]喵星球上的点名(后缀自动机+莫队+dfs序)
P2336 [SCOI2012]喵星球上的点名 名字怎么存?显然是后缀自动机辣 询问点到多少个喵喵喵其实就是 查询后缀自动机上parent树的一个子树 于是我们考虑莫队 怎么树上莫队呢 我们用dfs序 ...
- 洛谷 P2336 [SCOI2012]喵星球上的点名 解题报告
P2336 [SCOI2012]喵星球上的点名 题目描述 a180285 幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有 \(N\) 个喵星人,每个喵星人的 ...
- Luogu P2336 [SCOI2012]喵星球上的点名
题目链接 \(Click Here\)_ \(200\)行纯干货的代码,一发\(WA\)掉真的是让人窒息,幸好最后找到了锅在哪.(差点就要弃掉了\(QAQ\)) [调出来的时候真的是要高兴到哭出来了\ ...
- Luogu2336 SCOI2012 喵星球上的点名 SA、莫队
传送门 一道很套路的题目 先将所有串拼在一起,两个不同的串之间放一个没有出现在任何串中的字符做分隔,然后SA 那么对于所有点名串能够点到的名字串在SA中对应一段区间 把这些区间拿出来然后莫队统计每一个 ...
- 洛谷P2336 [SCOI2012]喵星球上的点名(后缀数组+莫队)
我学AC自动机的时候就看到了这题,想用AC自动机结果被学长码风劝退-- 学后缀数组时又看到了这题--那就写写后缀数组做法吧 结果码风貌似比当年劝退我的学长还毒瘤啊 对所有的模式串+询问串,不同串之间用 ...
- BZOJ2754: [SCOI2012]喵星球上的点名
2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 680 Solved: 314[Submit][Sta ...
- BZOJ 2754: [SCOI2012]喵星球上的点名 [后缀数组+暴力]
2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 1906 Solved: 839[Submit][St ...
随机推荐
- leetcode347 —— n中topK && PriorityQueue(Heap) && Map遍历
题目要求:求前K个最频繁出现的数字. 1.很容易想到,使用HashMap<Integer,Integer>来存储<number,frequency>键值对 1 int n = ...
- Java:线程池
Java:线程池 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 获取多线程的方法: 实现 Runnable 接口 实现 Callable 接口 实例化 Thre ...
- ruby基本图片上传
图片上传问题 在我们的项目里,需要实现海报的图片上传,便于更好地向外界展示一个社团活动的基本内容,但是在处理中间件相关问题时遇到了一点小小的挫折.不过这并不要紧,OSS对象存储服务固然好,但是本着交完 ...
- oo第三次博客-JML规格
这三周的作业主要是围绕以JML来约束代码开发,以确保程序的正确性与鲁棒性. Part 1:三次作业的实现与bug 第一次作业没有任何算法和数据结构上的难度,对于Path和PathContainer的各 ...
- 一文带你掌握【TCP拥塞窗口】原理
❝ 关注公众号:高性能架构探索.后台回复[资料],可以免费领取 ❞ 学过网络相关课程的,都知道TCP中,有两个窗口: 滑动窗口(在我们的上一篇文章中有讲),接收方通过通告发送方自己的可以接受缓冲区大小 ...
- 【做题记录】[NOIP2011 提高组] 观光公交
P1315 [NOIP2011 提高组] 观光公交 我们想在 \(k\) 次加速每一次都取当前最优的方案加速. 考虑怎样计算对于每一条边如果在当前情况下使用加速器能够使答案减少的大小. 如果当前到达某 ...
- uvm_subscriber
subscriber是消费,用户的意思 uvm_subscriber主要作为coverage的收集方式之一 uvm_subscriber的代码非常简单,继承于uvm_component,再加上一个an ...
- 记一次线上环境 ES 主分片为分配故障
故障前提 ElasticSearch 版本:5.2 集群节点数:5 索引主分片数:5 索引分片副本数:1 线上环境ES存储的数据量很大,当天由于存储故障,导致一时间 5个节点的 ES 集群,同时有两个 ...
- jmeter 数据库压力测试之MySql
1.首先下载合适的数据库驱动:https://mvnrepository.com/artifact/mysql/mysql-connector-java 2.创建testplan,并添加jar包 3. ...
- building sasl.wrapper extention
yum install gcc-c++ python-devel.x86_64 cyrus-sasl-devel.x86_64 pip install pyhs2 ref: https://www.o ...