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 ...
随机推荐
- diff算法深入一下?
文章转自豆皮范儿-diff算法深入一下 一.前言 有同学问:能否详细说一下 diff 算法. 简单说:diff 算法是一种优化手段,将前后两个模块进行差异化比较,修补(更新)差异的过程叫做 patch ...
- 关于java socket中的read方法阻塞问题
客户端: public class TCPClient { public static void main(String[] args) throws IOException { FileInputS ...
- OO第二单元电梯作业总结
目录 目录一.第一次作业分析设计策略基于度量分析程序结构二.第二次作业分析设计策略基于度量分析程序结构三.第三次作业分析设计策略基于度量分析程序结构四.分析自己程序的bug五.发现别人程序bug所采用 ...
- Spring Authorization Server的使用
Spring Authorization Server的使用 一.背景 二.前置知识 三.需求 四.核心代码编写 1.引入授权服务器依赖 2.创建授权服务器用户 3.创建授权服务器和客户端 五.测试 ...
- pyqgis环境配置
配置pyqgis开发环境时,很多网上教程写的非常繁琐,这里仅仅找了一个最简单的配置方法,使用pycharm的IDE,安装QGIS软件后,在pycharm的ProjectInterpreter里面填写Q ...
- (继承)Program2.1
覆盖和重写的意思是一样?结果是一样的 例如: 1 class Parent: # 定义父类 2 def myMethod(self): 3 print('调用父类方法') 4 5 6 class Ch ...
- 大型DELETE(删除大量数据)的一种解决方案
通过执行单条DELETE语句来删除一个大型的数据集会有以下的缺点: 1.DELETE语句的操作要被完整地记录到日志中,这要求在事务日志中要有足够的空间以完成整个事务: 2.在删除操作期间(可能会花费很 ...
- xiaoxiaole
common.cpp #include "common.h" common.h #ifndef COMMON_H_INCLUDED #define COMMON_H_INCLUDE ...
- 51nod_1006 最长公共子序列,输出路径【DP】
题意: 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个 ...
- PWN二进制漏洞学习指南
目录 PWN二进制漏洞学习指南 前言 前置技能 PWN概念 概述 发音 术语 PWN环境搭建 PWN知识学习途径 常见漏洞 安全机制 PWN技巧 PWN相关资源博客 Pwn菜鸡小分队 PWN二进制漏洞 ...