【bzoj2754】【scoi2012】喵星球上的点名
题解们:
- 1.首先可以被很多暴力给搞过去;我以前也是这样水过去的
- 2.ac自动机
- 2.1
- 抽离fail树
- 对点名建自动机,建$fail$树的时候只保留询问节点;
- 对于一个喵,子串==在自动机里匹配到的所有节点的$fail$祖先并
- 把姓和名都放到里面去跑,得到所有的点,需要把这些点在新的$fail$树里的祖先全部标记
- 具体按照dfs序排序,每个点$q[i]$的贡献就是$lca(q[i-1],q[i])$到$q[i]$那段
- 统计第一问用树上差分$q[i]$处$++$,$lca(q[i-1],q[i])$处$--$,具体第二问直接记录每个点到根有多少次点名统计直接相减;
- $O(N \ log N)$
#include<bits/stdc++.h>
#define rg register
#define il inline
#define Run(i,l,r) for(rg int i=l;i<=r;i++)
#define Don(i,l,r) for(rg int i=l;i>=r;i--)
using namespace std;
const int N=;
int n,m,o=,hd[N],a[N],b[N],s[N],tot,fl[N],fa[N],st[N],ed[N],idx;
int val[N],vis[N],que[N],head,tail,sz,cnt,size[N],tp[N],dep[N],pos[N],ans[N],deep[N];
il bool cmp(const int&x,const int&y){return st[x]<st[y];}
map<int,int>ch[N];
map<int,int>::iterator it;
struct Edge{int v,nt;}E[N];
il void adde(int u,int v){E[o]=(Edge){v,hd[u]};hd[u]=o++;}
il char gc(){
static char*p1,*p2,s[];
if(p1==p2)p2=(p1=s)+fread(s,,,stdin);
return(p1==p2)?EOF:*p1++;
}
il int rd(){
int x=,f=; char c=gc();
while(c<''||c>''){if(c=='-')f=-;c=gc();}
while(c>=''&&c<='')x=(x<<)+(x<<)+c-'',c=gc();
return x*f;
}
void get_fl(){
for(it=ch[].begin();it!=ch[].end();it++){
int v=it->second;
que[++tail]=v;
if(vis[v])adde(fa[v],v),size[v]=;
}
while(head < tail){
int u=que[++head];
for(it = ch[u].begin();it!=ch[u].end();it++){
int v = it->second, c = it->first, w=fl[u];
while(w&&!ch[w].count(c))w=fl[w];
if(!ch[w].count(c))fl[v]=;
else fl[v]=ch[w][c];
if(vis[fl[v]])fa[v]=fl[v];
else fa[v]=fa[fl[v]];
que[++tail]=v;
if(vis[v])adde(fa[v],v),size[v]=;
}
}
Don(i,tail,)size[fa[que[i]]]+=size[que[i]];
}
void dfs(int u,int T){
int son=;
st[u]=++idx;tp[u]=T;
dep[u]=dep[fa[u]]+;
deep[u]=deep[fa[u]]+vis[u];
for(int i=hd[u];i;i=E[i].nt){
int v=E[i].v;
if(!son||size[v]>size[son])son=v;
}
if(son)dfs(son,T);
for(int i=hd[u];i;i=E[i].nt){
int v=E[i].v;
if(son!=v)dfs(v,v);
}
ed[u]=idx;
}
il int lca(int x,int y){
int tx=tp[x],ty=tp[y];
while(tx!=ty){
if(dep[tx]<dep[ty])y=fa[ty],ty=tp[y];
else x=fa[tx],tx=tp[x];
}
return dep[x]<dep[y]?x:y;
}
void find(int len){
int x = , c;
Run(i,,len){
c = s[tot+i];
while(x&&!ch[x].count(c))x=fl[x];
if(!ch[x].count(c))x=;
else x=ch[x][c];
if(vis[x])que[++tail]=x;
else if(fa[x])que[++tail]=fa[x];
}
tot+=len;
}
void dfs(int u){
for(int i=hd[u];i;i=E[i].nt)
dfs(E[i].v),val[u]+=val[E[i].v];
}
int main(){
freopen("bzoj2754.in","r",stdin);
freopen("bzoj2754.out","w",stdout);
n=rd(); m=rd();
Run(i,,n){
a[i]=rd();Run(j,,a[i])s[++tot]=rd();
b[i]=rd();Run(j,,b[i])s[++tot]=rd();
}
Run(i,,m){
int x=rd(),u=,y;
Run(j,,x){
if(!ch[u][y=rd()])ch[u][y]=++sz;
u=ch[u][y];
}
vis[pos[i]=u]++;
}
get_fl();
dfs(,);
tot=;
Run(i,,n){
tail=;
find(a[i]),find(b[i]);
if(!tail)continue;
sort(que+,que+tail+,cmp);
head=;
Run(j,,tail){
if(!head||ed[que[j]]>ed[que[head]])head++;
que[head]=que[j];
}
tail=head;
val[]--,val[que[]]++;
ans[i]+=deep[que[]];
Run(j,,tail){
int tmp = lca(que[j-],que[j]);
val[tmp]--,val[que[j]]++;
ans[i] += deep[que[j]] - deep[tmp];
}
}
dfs();
Run(i,,m)printf("%d\n",val[pos[i]]);
Run(i,,n)printf("%d ",ans[i]);
return ;
}AC
- 3.后缀数组
- 3.1
- 莫队
- 将所有串用互不相等的连接符链接,为了方便让点名串后的连接符尽量小;
- 可以在SA里求出每次点名的区间,是后缀$i \ rank[i]$向后的一段;
- 问题变成统计 ①一个线段里有多少种颜色的点和 ②一种颜色的点被多少条线段覆盖;
- ①莫队模板;
- ②因为每个区间都是不同的,考虑差分,每次从莫队的区间里加入一个颜色就加上剩余的区间数,删去就减掉,就统计了中间出现的那段区间数;
- $O(N \ \sqrt N)$
- 3.2
- 树状数组
- SA的部分一样
- ①扫一遍,$pre[i]$表示倒着上一个扫到的和sa[i]颜色相同的位置,遇到一个每次$add(i,1)$,$add(pre[i],-1)$,直接统计对应区间;
- ②扫一遍,对于区间$[L,R]$,在$R$的位置$add(R,1)$ ,$L-1$的位置$add(L,-1)$ ,统计$i$到$pre[i]-1$的数量;
- 均可树状数组维护
- $O(N \ log N)$
#include<bits/stdc++.h>
#define il inline
#define rg register
using namespace std;
const int N=,M=;
int n,m,len,cnt,s[N],sub[N],tot,sa[N],ht[N],rk[N],pre[N],bl[N],pos[N],f[N][M],bin[M],l[N],mp[N],c[N],ans1[N],ans2[N];
struct data{
int x,y,z;
bool operator <(const data&A)const{return x < A.x;};
}p[N];
il char gc(){
static char*p1,*p2,s[];
if(p1==p2)p2=(p1=s)+fread(s,,,stdin);
return(p1==p2)?EOF:*p1++;
}
il int rd(){
int x=,f=; char ch=gc();
while(ch<''||ch>''){if(ch=='-')f=-;ch=gc();}
while(ch>=''&&ch<='')x=(x<<)+(x<<)+ch-'',ch=gc();
return x*f;
}
il void add(int x,int y){for(rg int i=x+;i<=len;i+=i&-i)c[i]+=y;}
il int que(int x){int re=;for(rg int i=x+;i;i-=i&-i)re+=c[i];return re;}
void discretize(){
sort(sub,sub+tot);
tot=unique(sub,sub+tot)-sub;
for(rg int i=;i<len;i++)s[i]=lower_bound(sub,sub+tot,s[i])-sub;
}
void build_sa(){
static int x[N],y[N],w[N];
for(rg int i=;i<len;i++)w[x[i]=s[i]]++;
for(rg int i=;i<tot;i++)w[i]+=w[i-];
for(rg int i=len-;~i;i--)sa[--w[x[i]]]=i;
for(rg int k=;k<len;k<<=){
int p = ;
for(rg int i=len-k;i<len;i++)y[p++]=i;
for(rg int i=;i<len;i++)if(sa[i]>=k)y[p++]=sa[i]-k;
for(rg int i=;i<tot;i++)w[i]=;
for(rg int i=;i<len;i++)w[x[i]]++;
for(rg int i=;i<tot;i++)w[i]+=w[i-];
for(rg int i=len-;~i;i--)sa[--w[x[y[i]]]]=y[i];
swap(x,y);
x[sa[]]=; p=;
for(rg int i=;i<len;i++){
x[sa[i]] = y[sa[i]]==y[sa[i-]]&&y[sa[i]+k]==y[sa[i-]+k] ? p - : p++;
}
if(p==len)break;
tot = p + ;
}
}
void build_ht(){
for(rg int i=;i<len;i++)rk[sa[i]]=i;
for(rg int i=,k=,j;i<len;i++){
if(k)k--;
j=sa[rk[i]-];
while(s[j+k]==s[i+k])k++;
f[rk[i]][]=ht[rk[i]]=k;
}
}
void build_rmq(){
for(rg int i=;i<;i++)
for(rg int j=;j<=len-bin[i];j++){
f[j][i] = min(f[j][i-],f[j+bin[i-]][i-]);
}
}
int main(){
freopen("bzoj2754.in","r",stdin);
freopen("bzoj2754.out","w",stdout);
for(int i=bin[]=;i<;i++)bin[i]=bin[i-]<<;
n=rd(); m=rd();
for(rg int i=,x;i<=n;i++){
x=rd();
for(rg int j=;j<=x;j++){
bl[len]=i;
sub[tot++]=s[len++]=rd();
}sub[tot++]=s[len]=-len,len++;
x=rd();
for(rg int j=;j<=x;j++){
bl[len]=i;
sub[tot++]=s[len++]=rd();
}sub[tot++]=s[len]=-len,len++;
}
for(rg int i=,x;i<=m;i++){
l[i]=x=rd();
bl[len]=-i;
for(rg int j=;j<=x;j++){
sub[tot++]=s[len++]=rd();
}sub[tot++]=s[len]=-len,len++;
}
discretize();
build_sa();
build_ht();
build_rmq();
for(rg int i=len-;~i;i--)if(bl[sa[i]]>){
int x = bl[sa[i]];
if(!mp[x])add(mp[x]=i,);
else{
pre[i]=mp[x],mp[x]=i;
add(pre[i],-),add(i,);
}
}else if(bl[sa[i]]<){
int x=-bl[sa[i]],y=i+;
for(rg int j=;~j;j--)if(f[y][j]>=l[x])y+=bin[j];
p[++cnt]=(data){y-,y-,};
p[++cnt]=(data){i-,y-,-};
ans1[x] = que(y-) - que(i-);
}
memset(c,,sizeof(c));
sort(p+,p+cnt+);
for(rg int i=len-,j=cnt;~i;i--){
while(j&&p[j].x==i)add(p[j].y,p[j].z),j--;
if(bl[sa[i]]>){
if(!pre[i])pre[i]=len;
ans2[bl[sa[i]]] += que(pre[i]-) - que(i-);
}
}
for(rg int i=;i<=m;i++)printf("%d\n",ans1[i]);
for(rg int i=;i<=n;i++)printf("%d ",ans2[i]);
return ;
}SA+BIT
- 4.后缀自动机
- 4.1
- 广义后缀自动机
- 至少这题和$SAM$差不多,只是新加一个单词重置$last$节点;
- 对点名串建出$SAM$之后,把姓名串在上面跑,对走过的点沿$parent$树向上跳可以找到所有子串,标记是否来过暴力统计;
- 这样两个问的方法是一样的
- $O(N \ \sqrt N )$
- 4.2
- 但其实如果建出$parent$树的话就和$1$一样,如果对$parent$树做$dfs$序维护的话就和$2$一样了,不说了;
#include<bits/stdc++.h>
#define rg register
#define il inline
using namespace std;
const int N=;
int n,m,s[N],tot,a[N],b[N],lst,cnt,pa[N],len[N],sum[N],val[N],vis[N],ans;
map<int,int>ch[N];
il char gc(){
static char*p1,*p2,s[];
if(p1==p2)p2=(p1=s)+fread(s,,,stdin);
return(p1==p2)?EOF:*p1++;
}
il int rd(){
int x=,f=; char c=gc();
while(c<''||c>''){if(c=='-')f=-;c=gc();}
while(c>=''&&c<='')x=(x<<)+(x<<)+c-'',c=gc();
return x*f;
}
il void ins(int x){
int p=lst,np; len[np=lst=++cnt]=len[p]+;
while(p&&!ch[p][x])ch[p][x]=np,p=pa[p];
if(!p){pa[np]=;return;}
int q = ch[p][x];
if(len[q]==len[p]+)pa[np]=q;
else{
int nq=++cnt;
len[nq]=len[p]+;
ch[nq]=ch[q];
pa[nq]=pa[q]; pa[q]=pa[np]=nq;
while(p&&ch[p][x]==q)ch[p][x]=nq,p=pa[p];
}
}
il void update(int x,int y){while(x&&vis[x]!=y)sum[x]++,vis[x]=y,x=pa[x];}
il void query(int x,int y){while(x&&vis[x]!=y)ans+=val[x],vis[x]=y,x=pa[x];}
int main(){
freopen("lg2336.in", "r", stdin);
freopen("lg2336.out","w",stdout);
n=rd();m=rd();
cnt=;
for(rg int i=;i<=n;i++){
lst=;a[i]=rd();
for(rg int j=;j<=a[i];j++)ins(s[++tot]=rd());
lst=;b[i]=rd();
for(rg int j=;j<=b[i];j++)ins(s[++tot]=rd());
}
tot=;
for(rg int i=;i<=n;i++){
for(rg int j=,now=;j<=a[i];j++)update(now=ch[now][s[++tot]],i);
for(rg int j=,now=;j<=b[i];j++)update(now=ch[now][s[++tot]],i);
}
for(rg int i=,l,now;i<=m;i++){
l=rd();
now=;
for(rg int j=,x;j<=l;j++){
x=rd();
if(!now)continue;
if(!ch[now].count(x))now=;
else now=ch[now][x];
}
if(now)val[now]++;
printf("%d\n",sum[now]);
}
tot=;
for(rg int i=;i<=n;i++){
ans=;
for(rg int j=,now=;j<=a[i];j++)query(now=ch[now][s[++tot]],n+i);
for(rg int j=,now=;j<=b[i];j++)query(now=ch[now][s[++tot]],n+i);
printf("%d ",ans);
}
return ;
}SAM
- 就数据来看,最快的应该是$3.1$(我没写QAQ),再来就是$2.1,4.1,3.2$,,不算$map$的话$tarjan$写lca,理论最好的应该是$2.1$ ;
【bzoj2754】【scoi2012】喵星球上的点名的更多相关文章
- BZOJ2754: [SCOI2012]喵星球上的点名
2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 680 Solved: 314[Submit][Sta ...
- [BZOJ2754] [SCOI2012]喵星球上的点名解题报告|后缀数组
a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串来点名,每次读出一个串的 ...
- bzoj2754:[SCOI2012]喵星球上的点名(后缀自动机)
Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串 ...
- BZOJ2754: [SCOI2012]喵星球上的点名(AC自动机)
Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 2816 Solved: 1246[Submit][Status][Discuss] Descript ...
- BZOJ2754: [SCOI2012]喵星球上的点名(AC自动机/后缀自动机)
Description a180285幸运地被选做了地球到喵星球的留学生.他发现喵星人在上课前的点名现象非常有趣. 假设课堂上有N个喵星人,每个喵星人的名字由姓和名构成.喵星球上的老师会选择M个串 ...
- BZOJ2754 SCOI2012喵星球上的点名
绝世好题. 正当我犹豫不决时,hzwer说:“MAP!!!” 没错这题大大的暴力,生猛的stl,贼基尔爽,,ԾㅂԾ,, 由于我们求点名在名字中的子串个数,所以将点名建AC自动机,记录节点属于哪次点名, ...
- BZOJ2754 [SCOI2012]喵星球上的点名 SA+莫队+树状数组
题面 戳这里 题解 首先先把所有给出的姓名和询问全部接在一起,建出\(height\)数组. 某个串要包含整个询问串,其实就相当于某个串与询问串的\(lcp\)为询问串的长度. 而两个后缀\(Suff ...
- 【BZOJ2754】[SCOI2012]喵星球上的点名
[BZOJ2754][SCOI2012]喵星球上的点名 题面 bzoj 洛谷 题解 这题有各种神仙做法啊,什么暴力\(AC\)自动机.\(SAM\)等等五花八门 我这个蒟蒻在这里提供一种复杂度正确且常 ...
- BZOJ 2754: [SCOI2012]喵星球上的点名
2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 649 Solved: 305[Submit][Sta ...
- BZOJ 2754: [SCOI2012]喵星球上的点名 [后缀数组+暴力]
2754: [SCOI2012]喵星球上的点名 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 1906 Solved: 839[Submit][St ...
随机推荐
- GlusterFS分布式存储集群-2. 使用
参考文档: Quick Start Guide:http://gluster.readthedocs.io/en/latest/Quick-Start-Guide/Quickstart/ Instal ...
- ubuntu 相关软件设置
软件篇 1. 网易云音乐软件 首先去官网下载网易云音乐客户端linux版,网址:http://music.163.com/#/download,选择linux版本,然后选择ubuntu 16.04(6 ...
- Ruby知识点三:运算符
1.逻辑运算符 (1)条件1 || 条件2 条件1为假时,才需判断条件2 (2)条件1 && 条件2 条件1为真时,才需判断条件2 2.范围运算符 (1)x..y 从x到y,包括y ...
- which命令详解
基础命令学习目录首页 原文链接:https://www.cnblogs.com/jkin/p/10289085.html Linux which命令用于查找文件. which指令会在环境变量$PATH ...
- NodeJS http模块
Node.js提供了http模块,用于搭建HTTP服务端和客户端. 创建Web服务器 /** * node-http 服务端 */ let http = require('http'); let ur ...
- python循环综合运用
循环很重要,计算机很蠢,唯一的优势就是按照指令不停的执行,所以决定在说一下. break语句,用在循环体中,迫使循环立即终止,即跳出所在循环体,继续执行循环体后面的语句. sum=0 i=1 whil ...
- Vim YouCompleteMe 安装配置
YouCompleteMe是很强大的vim插件,可以提供强大的补齐功能,曾经多次尝试安装,都没有配置成功,最近在一个契机下,看到有同事的配置,自己在边尝试和边咨询后,终于也搞定了,遂记录下. 官网有最 ...
- sprint2(第九天)
今天是sprint2的最后一天,已经完成功能有可以实现点餐功能.菜品的添加和删减.菜品数量的增减.添加备注.查看订单详情.订单状态.提交订单.后厨可以查看订单信息,对菜品的状态进行操作,是否完成烹饪, ...
- HDU 3811 Permutation 状压dp
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3811 Permutation Time Limit: 6000/3000 MS (Java/Othe ...
- 【CSAPP笔记】6. 汇编语言——控制
原先刊于自己的域名下面,考虑到博客园之前发过一半,不想烂尾,故在博客园发一版. 到目前为止我们只考虑了直线代码的执行行为,也就是指令一条接着一条执行.C语言中的某些语句,比如条件语句.循环.分支语句, ...