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 ...
随机推荐
- 自动化键盘,python
国际惯例先上源https://github.com/boppreh/keyboard#键盘输入 from pynput.keyboard import Key,Controller,Listener ...
- Scrum Meeting 0501
零.说明 日期:2021-5-1 任务:简要汇报两日内已完成任务,计划后两日完成任务 一.进度情况 组员 负责 两日内已完成的任务 后两日计划完成的任务 qsy PM&前端 整装待发,准备冲刺 ...
- 需求存在,功能存在——Alpha阶段性总结
0.Alpha开发成果 题士Alpha发布报告 题士开发记录 1.任务划分 Alpha阶段大致将任务划分为Design,Develop和Test三类 Design型任务包含页面UI设计和接口API设计 ...
- 2021.7.27考试总结[NOIP模拟25]
罕见的改完了题 T1 random 一堆概率,一堆函数,一堆递归,一眼不可做, 但它只有一个参数,所以.. 熠神本着"只有20太难看"的心态,通过样例三个出规律,口胡了一波$\fr ...
- LP-DDR 和其他 DDR
一篇技術文檔比較 LP-DDR 和其他 DDR. 就觀念來說,LP-DDR 就是 Low Power 的 DDR:但就架構來說,LP-DDR 和其他 DDR 是截然不同的東西. 他們分屬不同的 JDE ...
- 利用Ambari平台安装与部署Hadoop
* 本篇是利用Ambari平台安装与部署Hadoop,如果需要原生部署Hadoop,请点击以下地址: https://www.cnblogs.com/live41/p/15467263.html 一. ...
- SpirngBoot整合Mybatis Plus多数据源
导读 有一个这样子的需求,线上正在跑的业务,由于业务发展需要,需重新开发一套新系统,等新系统开发完成后,需要无缝对接切换,当初具体设计见草图. 添加依赖 <!--lombok--> < ...
- LOTO虚拟示波器软件功能演示之——FIR数字滤波
本文章介绍一下LOTO示波器新出的功能--FIR数字滤波的功能. 在此之前我们先来了解一下带通滤波和带阻滤波.我们都知道每个信号是不同频率不同幅值正弦波的线性叠加,为了方便直接得观察到这种现象,就有了 ...
- ELK集群之kibana(4)
kibane安装及基础使用 Kibana的安装 Kibana包含前端展示.es操作简化 yum localinstall kibana-7.6.2-x86_64.rpm -y Kibana配置修改ki ...
- TDSQL | 在整个技术解决方案中HTAP对应的混合交易以及分析系统应该如何实现?
从主交易到传输,到插件式解决方案,每个厂商对HTAP的理解和实验方式都有自己的独到解法,在未来整个数据解决方案当中都会往HTAP中去牵引.那么在整个技术解决方案中HTAP对应的混合交易以及分析系统应该 ...