初三暑假题表重点
初三暑假题表重点复习
记忆
- 后缀数组
- 动态树
- AC机
- 树状数组
- pbds
- 全源最短路
- 网络流
并查集
种类并查集
-
过程
有多少个种类就开多少个并查集数组
每个并查集内部的连边表示\(x,y\)是同一种类,与外部并查集的连边表示\(x,y\)不是同一种类
这样就可以快速分别\(x,y\)是不是同一类
如图,这就是POJ1182中,当\(1,3\)同类,\(2\)吃\(4\),\(3\)吃\(2\)的时候的图
如果\(x,y\)是同类,那么\(1x,1y\)必须在一个集合里
如果\(x\)吃\(y\),那么\(1x,2y\)必须在一个集合里
-
复杂度
有\(n\)个点,\(m\)个种类,则空间复杂度为\(O(nm)\),每次查询修改的复杂度为\(O(m\log_en)\)。
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } void write(int sum) { if (sum<0) printf("-"),sum=-sum; if (sum==0) { putchar('0'),putchar(' '); return ; } char ch[20];int len=0; while (sum>0) ch[++len]=sum%10+'0',sum/=10; for (int i=len;i>=1;i--) putchar(ch[i]); putchar(' '); } int n,m,ans; int fa[10010*5*3]; int findfa(int x) { if (fa[x]==x) return x; else return fa[x]=findfa(fa[x]); } int main() { // freopen("M.in","r",stdin); // freopen("M.out","w",stdout); read(n),read(m); for (int i=1;i<=n*3;i++) fa[i]=i; // for (int i=1;i<=n;i++) fa[i]=fa[i+n]=fa[i+n*2]=i; for (int i=1;i<=m;i++) { int t,x,y;read(t),read(x),read(y); int xf1=findfa(x),yf1=findfa(y); int xf2=findfa(x+n),yf2=findfa(y+n); int xf3=findfa(x+n*2),yf3=findfa(y+n*2); if (x>n || y>n) { ans++; continue; } if (t==1) { if (xf1==yf2 || xf1==yf3) ans++; else { // printf("t1 : %d %d\n",x,y); fa[yf1]=xf1; fa[yf2]=xf2; fa[yf3]=xf3; } } else if (t==2) { if (xf1==yf1 || xf1==yf2) ans++; else { // printf("t2 : %d %d\n",x,y); fa[yf1]=xf2; fa[yf2]=xf3; fa[yf3]=xf1; } } } printf("%d",ans); // fclose(stdin); // fclose(stdout); return 0; }
带权并查集
-
过程
只开一个并查集,在每一次找父亲的时候维护一个关系数组,不过这个关系要满足可传递性
-
复杂度
空间\(O(n)\),每次查询修改时间\(O(\log_en)\)
三维偏序
-
过程
首先,按\(x\)从大到小排序,之后进行归并排序。
对于一个区间\([l,r]\),树状数组记录每个已经排好序的点的\(y\)出现了多少次,既\(sum(1,i)=\),已经排好序的点的\(y\)小于等于\(i\)的个数
让以\(z\)为关键字,对\([l,r]\)排序,对于每个逆序对(要求一个在\([l,mid]\),一个在\([mid+1,r]\)),这个点的\(ans\)就加上树状数组中,小于等于它的y的个数
-
复杂度
空间\(O(n)\),时间\(O(n\log_2^2n)\)
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; //read void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } //sort int n,mx; struct node1 { int x,y,z,cnt,ans,wh; }a[100010],f[100010],b[100010]; int s[100010]; int len; bool cmp1(node1 a,node1 b) { if (a.x!=b.x) return a.x<b.x; if (a.y!=b.y) return a.y<b.y; if (a.z!=b.z) return a.z<b.z; } //tree int lowbit(int x) { return x & -x; } struct tree { int a[2*100010]; void add(int x,int s) { for (int i=x;i<=mx;i+=lowbit(i)) a[i]+=s; } int sum(int x) { int res=0; for (int i=x;i>=1;i-=lowbit(i)) res+=a[i]; return res; } }tr1,tr2; void add(int x,int y,int z) { tr1.add(x,z),tr1.add(y+1,-z); tr2.add(x,x*z),tr2.add(y+1,-(y+1)*z); } int sum(int x) { return (tr1.sum(x)*(x+1)-tr2.sum(x)); } int sum(int x,int y) { return sum(y)-sum(x-1); } //qsort void qsort(int l,int r) { // dfs if (l>r) return ; if (l==r) return ; int mid=(l+r)/2; qsort(l,mid),qsort(mid+1,r); // sort int x=l,y=mid+1; // printf("l,r,mid,x,y : %d %d %d %d %d\n",l,r,mid,x,y); for (int i=l;i<=r;i++) { if (x>mid) b[i]=a[y]/*,printf("y1: %d %d %d\n",y,a[y].z)*/,b[i].ans+=sum(1,b[i].z)/*,printf("ans : %d\n",sum(1,b[i].z))*/,y++; else if (y>r) b[i]=a[x]/*,printf("x1: %d %d\n",x,a[x].z)*/,add(b[i].z,b[i].z,b[i].cnt),x++; else if (a[x].y<=a[y].y) b[i]=a[x]/*,printf("x2: %d %d\n",x,a[x].z)*/,add(b[i].z,b[i].z,b[i].cnt),x++; else if (a[x].y>a[y].y) b[i]=a[y]/*,printf("y2: %d %d\n",y,a[y].z)*/,b[i].ans+=sum(1,b[i].z)/*,printf("ans : %d\n",sum(1,b[i].z))*/,y++; } for (int i=l;i<=mid;i++) add(a[i].z,a[i].z,-a[i].cnt); for (int i=l;i<=r;i++) a[i]=b[i]; } int main() { // freopen("P3810_7.in","r",stdin); // freopen("P3870.out","w",stdout); // read read(n),read(mx); for (int i=1;i<=n;i++) read(f[i].x),read(f[i].y),read(f[i].z),f[i].wh=i,f[i].cnt=1; // sort & unique sort(f+1,f+n+1,cmp1); for (int i=1;i<=n;i++) if (f[i].x!=f[i-1].x || f[i].y!=f[i-1].y || f[i].z!=f[i-1].z) a[++len]=f[i]; else a[len].cnt++,f[i].wh=f[i-1].wh; // qsort /* printf("%d\n",len); for (int i=1;i<=len;i++) printf("a : %d %d %d\n",a[i].x,a[i].y,a[i].z); printf("\n"); */ qsort(1,len); // write for (int i=1;i<=len;i++) s[a[i].ans+a[i].cnt-1]+=a[i].cnt; for (int i=0;i<=n-1;i++) printf("%d\n",s[i]); // fclose(stdin);fclose(stdout); return 0; }
树状数组
一维树状数组
-
单点修改,区间查询
-
区间修改,单点查询
对原数组进行差分,即\(b[i]=a[i]-a[i-1]\),那么\(a[i]=\sum_{j=1}^{i}b[i]\),
那么区间\([l,r]\)加上\(x\),实际上就是\(b[l]=b[l]+x\),但因为不能影响\(r\)后面的\(b[i]\),所以\(b[r+1]=b[r+1]-x\)
那么\(a[i]=sum(1,i)\)
-
区间修改,区间查询
这个问题,可以在\(2\)的基础上思考一下,如何求前缀和的前缀和,
\[\huge\sum_{j=1}^{x}\sum_{k=1}^{j}b[k] \]将\(j\)移到外面,发现每个\(b[j]\)被算了\(j\)次
\[\huge \sum_{j=1}^{x}b[j]*(x-j+1) \\ \huge =(x+1)\times\sum_{j=1}^{x}b[j]-\sum_{i=1}^{x}b[j]\times j \]那么只需要维护两个树状数组\(b[j]\),和\(b[j]\times j\)就行了
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define int long long using namespace std; void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } int lowbit(int x) { return x & -x; } int n,m; struct tree { int a[100010]; void init() { memset(a,0,sizeof(a)); } void add(int x,int s) { for (int i=x;i<=n;i+=lowbit(i)) a[i]+=s; } int sum(int x) { int res=0; for (int i=x;i>=1;i-=lowbit(i)) res+=a[i]; return res; } }tr1,tr2; void add(int x,int y,int z) { tr1.add(x,z),tr1.add(y+1,-z); tr2.add(x,z*x),tr2.add(y+1,-z*(y+1)); } int sum(int x) { return tr1.sum(x)*(x+1)-tr2.sum(x); } int sum(int x,int y) { return sum(y)-sum(x-1); } signed main() { // freopen("M.in","r",stdin); // freopen("M.out","w",stdout); read(n),read(m); for (int i=1;i<=n;i++) { int x; read(x); add(i,i,x); } for (int i=1;i<=m;i++) { int t,x,y,k;read(t); if (t==1) read(x),read(y),read(k),add(x,y,k); if (t==2) read(x),read(y),printf("%lld\n",sum(x,y)); } // fclose(stdin);fclose(stdout); return 0; }
二维树状数组
-
单点修改,区间查询
-
区间修改,单点查询
同上,不过差分要这样做
\[b[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1] \]那么
\[\sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]=a[x][y] \]修改的时候就要把\(b[x][y]=b[x][y]+c,b[u+1][y]=b[u+1][y]-c,b[x][v+1]=b[x][v+1]-c,b[u+1][v+1]=b[u+1][v+1]+c\)
-
区间修改,区间查询
也是同上,思考如今计算
\[\huge \sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{k=1}^{i}\sum_{w=1}^{j}b[k][w] \]同样提出看看每个被算了多少次
\[\huge \sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]\times(x-i+1)\times(y-j+1)\\ \huge = (x+1) (y+1)\sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]\\ \huge -(x+1)\sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]\times j\\ \huge -(y+1)\sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]\times i\\ \huge +\sum_{i=1}^{x}\sum_{j=1}^{y}b[i][j]\times i\times j \]维护四个树状数组即可
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define lowbit(x) (x&(-x)) using namespace std; void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } void write(int sum) { if (sum<0) printf("-"),sum=-sum; if (sum==0) { putchar('0'),putchar(' '); return ; } char ch[20];int len=0; while (sum>0) ch[++len]=sum%10+'0',sum/=10; for (int i=len;i>=1;i--) putchar(ch[i]); putchar(' '); } int n,m; struct node { int a[1010][1010]; void add(int x,int y,int s) { for (int i=x;i<=n;i+=lowbit(i)) for (int j=y;j<=m;j+=lowbit(j)) a[i][j]+=s; } int sum(int x,int y) { int s=0; for (int i=x;i>=1;i-=lowbit(i)) for (int j=y;j>=1;j-=lowbit(j)) s+=a[i][j]; return s; } }tr1,tr2,tr3,tr4; void add(int x,int y,int z,int w,int s) { tr1.add(x,y,s),tr1.add(x,w+1,-s),tr1.add(z+1,y,-s),tr1.add(z+1,w+1,s); tr2.add(x,y,s*x),tr2.add(x,w+1,-s*x),tr2.add(z+1,y,-s*(z+1)),tr2.add(z+1,w+1,s*(z+1)); tr3.add(x,y,s*y),tr1.add(x,w+1,-s*(w+1)),tr1.add(z+1,y,-s*y),tr1.add(z+1,w+1,s*(w+1)); tr1.add(x,y,s*x*y),tr1.add(x,w+1,-s*x*(w+1)),tr1.add(z+1,y,-s*(z+1)*y),tr1.add(z+1,w+1,s*(z+1)*(w+1)); } int sum(int x,int y) { return (x+1)*(y+1)*tr1.sum(x,y)-(y+1)*tr2.sum(x,y)-(x+1)*tr3.sum(x,y)+tr4.sum(x,y); } int sum(int x,int y,int z,int w) { return sum(z,w)-sum(z,y-1)-sum(x-1,w)+sum(x-1,y-1); } int main() { // freopen("M.in","r",stdin); // freopen("M.out","w",stdout); // fclose(stdin); // fclose(stdout); return 0; }
后缀数组
过程
这个过程,是要求出一个字符串\(s\)所有后缀排序的结果,以及排名为\(i\)的后缀,与排名为\(i-1\)的后缀,的最长公共前缀的长度。
特别的,当\(i=1\)时,长度为\(0\),我们把这个长度组成的数组设为\(h\),即\(h[i]\)表示排名为\(i\)的后缀,与排名为\(i-1\)的后缀,的最长公共前缀的长度。
首先,要求排序,设\(rk[i]\)表示以\(s[i]\)开头的后缀的排名,\(sa[i]\)表示排名为\(i\)的后缀的开头位置。
举个例子,当\(s=aabaaaab\),后缀为\(aabaaaab,abaaaab,baaaab,aaaab,aaab,aab,ab,b\)
把每个后缀长度补成相同的\(n\)(在后面加字典序最小的字符),设两个要比较的后缀分别为\(x,y\)
那么设\(mid=\frac{n}{2}\),如果我们知道\(x[1\to mid],x[mid+1 \to n],y[1 \to mid],y[mid+1 \to n]\)的大小,那我们就知道的了\(x,y\)的大小,于是明显二分长度
有两个关键字,想到基数排序,复杂度\(O(n \log_2 n)\),就可完成排序,实现时维护出\(rk,sa\)。实际上有\(O(n)\)的DC3做法,十分复杂,我不会
最后来做出\(h\)数组,表示第\(i\)名的后缀与它前一名的后缀的最长公共前缀。
引理:\(h[rk[i]] \geq h[rk[i-1]]-1\)
证明:
当\(h[rk[i-1]] \leq 1\)时,显然成立
当\(h[rk[i-1]] > 1\)时,设后缀\(i-1\)为 \(aAD\)( 是一个长度为\(h[rk[i-1]]-1\) 的字符串),那么后缀 \(i\)就是\(AD\) 。设后缀 \(sa[rk[i-1]-1]\)为\(aAB\) ,那么\(LCP(i-1,sa[rk[i-1]-1])=aA\) 。由于后缀\(AB\) 是 ,一定排在后缀\(i\) 的前面,所以后缀 \(sa[rk[i]-1]\)一定含有前缀\(A\) ,所以\(LCP(i,sa[rk[i]-1])\) 至少是\(h[rk[i-1]]-1\) 。
根据引理,暴力即可求出\(h\)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void write(int sum)
{
if (sum<0) printf("-"),sum=-sum;
if (sum==0) { putchar('0'),putchar(' '); return ; }
char ch[20];int len=0;
while (sum>0) ch[++len]=sum%10+'0',sum/=10;
for (int i=len;i>=1;i--) putchar(ch[i]);
putchar(' ');
}
int n;
struct node { int x,y,z; }a[3000000],b[3000000];
char s[3000000];
int rk[3000000],sa[3000000];
int bz[3000000],h[3000000];
bool cmp(node a,node b) { return a.y<b.y; }
bool cmp2(node a,node b)
{
if (a.y!=b.y) return a.y<b.y;
else return a.z<b.z;
}
void sortm()
{
for (int i=1;i<=n;i++) a[i].x=i,a[i].y=s[i];
sort(a+1,a+n+1,cmp);
int now=0;
a[0].y=-1<<30;
for (int i=1;i<=n;i++)
{
if (a[i].y!=a[i-1].y) now++;
rk[a[i].x]=now;
sa[now]=a[i].x;
}
for (int i=n+1;i<=3000000-1;i++) rk[i]=0;
memset(bz,0,sizeof(bz));
for (int ln=2;ln<=n*2;ln*=2)
{
for (int i=1;i<=n;i++)
a[i].z=rk[a[i].x],a[i].y=rk[a[i].x+ln/2];
memset(bz,0,sizeof(bz));
for (int i=1;i<=n;i++) bz[a[i].y]++;
for (int i=1;i<=n;i++) bz[i]+=bz[i-1];
for (int i=n;i>=1;i--)
{
b[bz[a[i].y]]=a[i];
bz[a[i].y]--;
}
memset(bz,0,sizeof(bz));
for (int i=1;i<=n;i++) bz[b[i].z]++;
for (int i=1;i<=n;i++) bz[i]+=bz[i-1];
for (int i=n;i>=1;i--)
{
a[bz[b[i].z]]=b[i];
bz[b[i].z]--;
}
a[0].y=a[0].z=-1<<30;
now=0;
for (int i=1;i<=n;i++)
{
if (a[i].y==a[i-1].y && a[i].z==a[i-1].z) rk[a[i].x]=now;
else rk[a[i].x]=++now;
}
}
for (int i=1;i<=n;i++) sa[i]=a[i].x;
int k=0;
for (int i=1;i<=n;i++)
{
if (k) k--;
while (s[i+k]==s[sa[rk[i]-1]+k]) k++;
h[rk[i]]=k;
}
}
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
scanf("%s",s+1);
n=strlen(s+1);
sortm();
// fclose(stdin);
// fclose(stdout);
return 0;
}
应用
-
任意两个子串的LCP
\[LCP(sa[i],sa[j])=\min_{k=i+1}^{i}h[k] \] -
不同子串的数目
\[sum=\sum_{i=1}^{n}h[i] \] -
出现至少 \(k\) 次的子串的最大长度
二分子串的长度,看看有没有连续的\(h[i]\)大于等于子串长度,且连续长度大于等于\(k\)的
-
是否有某字符串在文本串中至少不重叠地出现了两次
同样二分子串长度,看看有没有连续\(h[i]\)大于等于子串长度,且不相交。
树分治
当要处理大规模的树上路径信息问题,可以考虑点分,或边分。
点分治
过程
- 找重心
- 统计儿子信息
- 信息合并
- 递归儿子
复杂度
因为是重心,所以最多有\(\log_2n\)层,每个点都要递归一遍,所以\(O(n\log_2^2n)\)
边分治
过程
-
把树变为二叉树如图
-
找到重边
-
统计儿子信息
-
信息合并
-
递归儿子
复杂度
因为变为二叉树,最多增加\(n\)个点,每次把子树大小除\(2\),所以复杂度\(O(n\log_2 n)\)
//洛谷模板点分指
#include<map>
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void write(int sum)
{
if (sum<0) printf("-"),sum=-sum;
if (sum==0) { putchar('0'),putchar(' '); return ; }
char ch[20];int len=0;
while (sum>0) ch[++len]=sum%10+'0',sum/=10;
for (int i=len;i>=1;i--) putchar(ch[i]);
putchar(' ');
}
int n,m;
struct point { int x,bz,sz,nw,nm,ds; }p[10010];
struct edge { int x,y,c,next; }a[10010*2];
int len,last[10010];
void ins(int x,int y,int c)
{
len++,a[len].x=x,a[len].y=y,a[len].c=c;
a[len].next=last[x],last[x]=len;
}
void find_centreG(int x,int fa,int n)
{
p[x].sz=1;
int nm=-1<<30;
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if (p[y].bz==0)
{
if (y!=fa)
{
find_centreG(y,x,n);
nm=max(nm,p[y].sz);
p[x].sz+=p[y].sz;
}
}
}
nm=max(nm,n-p[x].sz);
int nw=x;
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if (p[y].bz==0)
{
if (y!=fa)
{
if (nm>p[y].nm)
nw=p[y].nw,nm=p[y].nm;
}
}
}
p[x].nw=nw;
p[x].nm=nm;
}
struct node { int x,n; };
int ans;
queue<node> q;
int lst[10010],hd;
int s[10000020];
int g[10010],gl=0;
void find_road(int x,int fa,int tp)
{
for (int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
if (y!=fa && p[y].bz==0)
{
p[y].ds=p[x].ds+a[k].c;
if (p[y].ds>tp) continue ;
find_road(y,x,tp);
lst[++hd]=p[y].ds;
}
if (fa==-1)
{
for (int i=1;i<=hd;i++)
{
if (s[tp-lst[i]]==1 || lst[i]==tp)
{
ans=1;
return ;
}
}
for (int i=1;i<=hd;i++)
s[lst[i]]=1,g[++gl]=lst[i];
hd=0;
}
}
}
void DAC(int tp)
{
ans=0;
for (int i=1;i<=n;i++) p[i].bz=0;
while (q.empty()==false) q.pop();
q.push((node){1,n});
while (q.empty()==false)
{
node now=q.front();q.pop();
// printf("%d\n",now.x);
find_centreG(now.x,-1,now.n);
int x=p[now.x].nw;
// printf("g : %d\n",x);
hd=0;gl=0;
p[x].ds=0;
find_road(x,-1,tp);
for (int i=1;i<=gl;i++) s[g[i]]=0;
gl=0;
p[x].bz=1;
for (int k=last[x];k;k=a[k].next)
if (p[a[k].y].bz==0)
q.push((node){a[k].y,p[a[k].y].sz});
if (ans==1) return ;
}
}
int main()
{
// freopen("P3806_1.in","r",stdin);
// freopen("M.out","w",stdout);
read(n),read(m);
for (int i=1;i<=n-1;i++)
{
int x,y,c;read(x),read(y),read(c);
ins(x,y,c),ins(y,x,c);
}
while (m--)
{
int k;read(k);
DAC(k);
if (ans==1) printf("AYE\n");
else printf("NAY\n");
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
/*
7 10
1 6 13
6 3 9
3 5 7
4 1 3
2 4 20
4 7 2
*/
使用范围
当信息在点上时,建议使用点分治
当信息在边上时,建议使用边分治
LIS
子序列
做法很多,这里使用一个快速且可以求路径的DP方法
- 将\(a\)中的元素离散化
- 设\(f[i]\)表示以\(a[i]\)结尾的LIS的长度,\(t[i]\)表示后面的元素的位置
- 用线段树查询\(1 \to a[i]\)之间出现的最长长度,\(f[i]=\max_{k=1}^{a[i]}tr[k],t[i]=k\)
这样时间复杂度为\(O(n\log_2 n)\) ,且可以在\(O(n)\)时间复杂度内找到路径。
//不可求路径的二分做法,但简洁
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void writes(char s[],int len) { for (int i=1;i<=len;i++) printf("%c",s[i]); }
int n,a[100100];
int len,s[100100];
int main()
{
freopen("LIS.in","r",stdin);
freopen("LIS.out","w",stdout);
read(n);
for (int i=1;i<=n;i++) read(a[i]);
len=1;s[len]=a[1];
for (int i=2;i<=n;i++)
{
if (s[len]<a[i]) s[++len]=a[i];
else
{
int l=1,r=len,mid;
while (l<r)
{
mid=(l+r)/2;
if (a[i]<=s[mid]) r=mid;
else l=mid+1;
}
s[l]=a[i];
}
}
printf("%d",len);
fclose(stdin);fclose(stdout);
return 0;
}
子串
可以通过单调队列\(O(n)\)做
LCS
子序列
\(S_1\)中有重复的字符
朴素的DP的时空复杂度都是\(O(n^2)\)的
\(f[i][j]\)表示\(S_1\)的前\(i\)个字符,与\(S_2\)的前\(j\)个字符,LCS
如果\(S_1[i]=S_2[j]\),那么\(f[i][j]=f[i-1][j-1]+1\)
否则,\(f[i][j]=max(f[i-1][j],f[i][j-1])\)
\(S_1\)中没有重复的字符
可以将\(S_1\)中的每一个字符设为它的位置\(i\)
然后将\(S_2\)中与\(S_1\)相同的字符替换为位置\(i\)
之后求一个LIS就可以了
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void writes(char s[],int len) { for (int i=1;i<=len;i++) printf("%c",s[i]); }
int n,m,ans=0;
int a[100100],b[100100];
int ch[100100][100],sum[100100100],len=0;
void LIS(int *a,int n,int &len)
{
int s[100100];memset(s,0,sizeof(s));
len=1;s[len]=a[1];
for (int i=2;i<=n;i++)
{
if (s[len]<a[i]) s[++len]=a[i];
else
{
int l=1,r=len,mid;
while (l<r)
{
mid=(l+r)/2;
if (a[i]<=s[mid]) r=mid;
else l=mid+1;
}
s[l]=a[i];
}
}
}
int main()
{
memset(ch,0,sizeof(ch));
//freopen("P1439.in","r",stdin);
//freopen("P1439.out","w",stdout);
read(n);m=n;
for (int i=1;i<=n;i++) read(a[i]);
for (int j=1;j<=m;j++) read(b[j]);
for (int i=1;i<=n;i++)
ch[a[i]][++ch[a[i]][0]]=i;
for (int j=1;j<=m;j++)
for (int i=ch[b[j]][0];i>=1;i--)
sum[++len]=ch[b[j]][i];
//for (int i=1;i<=len;i++) printf("%d ",sum[i]); printf("\n");
LIS(sum,len,ans);
printf("%d\n",ans);
//fclose(stdin);fclose(stdout);
return 0;
}
子串
DP是\(O(n^2)\)的,但可以通过后缀数组的方法实现\(O(n \log_2 n)\)
- 设\(S=S_1+'`'+S_2\)
- 后缀数组可以求出重复的子串,不过要判断一下开头是否在\('`'\)左右
LPS
子序列
可以通过区间DP\(O(n^2)\)做
对于区间\([l,r]\),如果\(s[l]=s[r]\),\(f[l][r]=f[l+1][r-1]+2\)
否则\(f[l][r]=\max(\max(f[l+1][r],f[l][r-1]),f[l+1][r-1])\)
子串
可以用manacher算法\(O(n)\)做,不建议用后缀数组\(O(n \log_2 n)\) 做
-
设字符串为\(S\),首先我们要寻找它的对称中心,但发现对称中心可能在一个字符上,也可能在两个字符中间
因为在两个字符中间的我们不好访问,于是我们可以在中间插入一些无关的字符,让字符来代替中间的空隙
-
我们记录一个数组\(P\),\(P_i\)表示以\(S_i\)为中心的回文数列的最大半径
如图,\(P_7=6\),在记录\(mid,r\),表示当前已知的\(P_i\)中,\(P_{mid}+r\)是最大的,即右区间离\(S_{S.length()}\)最近
-
于是对于一个未知的\(P_i\)如果\(i \leqslant P_{mid}+r\)那么就可以根据回文数的对称性,确定在\(mid-r \leqslant i \leqslant {mid}+r\)这段区间里,\(P_i=min(P_{关于mid对称的点},mid+r-i)\),易得对称点为\(2*mid-i\),于是\(P_i=min(P_{2*mid-i},mid+r-i)\)
-
但是我们不知道\(mid+r\)后面是否还有相同的\(P_x,P_y\),所以要暴力枚举,最后更新\(mid,r\)
-
于是最大值就是\(P_{max}\)
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } char s[11000002*2],g[11000002*2]; int p[11000002*2]; int len; int main() { // freopen("M.in","r",stdin); // freopen("M.out","w",stdout); s[++len]='|'; char ch=getchar(); while (ch!=EOF) { s[++len]=ch; s[++len]='|'; ch=getchar(); } int mmax=0; // for (int i=1;i<=len;i++) printf("%c",s[i]); for (int i=1,r=0,mid=0;i<=len;i++) { if (i<=mid+r) p[i]=min(p[2*mid-i],mid+r-i+1); while (i-p[i]>=1 && i+p[i]<=len && s[i-p[i]]==s[i+p[i]]) p[i]++; if (s[i-p[i]]!=s[i+p[i]] || i-p[i]<1 || i+p[i]>len) p[i]--; if (i+p[i]>r) r=p[i],mid=i; mmax=max(mmax,p[i]); } // printf("\n"); printf("%d",mmax); // fclose(stdin);fclose(stdout); return 0; }
四边形不等式
这是一个关于DP的优化,当区间DP时,要找最优分界点\(k\)
形如这样的DP式
如果\(\forall l \leq l' \leq r' \leq r,w(l',r')\leq w(l,r)\),那么则称函数\(w\)对于区间包含关系具有单调性。
如果\(\forall l_1\leq l_2 \leq r_2 \leq r_1,w(l_1,r_2)+w(l_2,r_1)\leq w(l_1,r_1)+w(l_2,r_2)\),那么则称函数\(w\)满足四边形不等式。
引理:如果\(w(l,r)\)满足四边形不等式和区间包含单调性,那么\(f[l][r]\)满足四边形不等式
可以用分类讨论+科学归纳法简单证明
定理:如果\(f\)满足四边形不等式,则记\(m_{l,r}\)为最优决策点
那么\(m_{l,r-1} \leq m_{l,r} \leq m_{l+1,r}\)
于是,我们边可以把一个\(O(n^3)\)的DP,优化为\(O(n^2)\)的DP,第三层循环减少了,可以证明
左偏树(可并堆)
应用
可以\(O(\log_2 n+\log_2 m)\)堆合并,还可以优化某些题(例如\(k\)短路就需要用到可持久化左偏树)
实现
定义一个点的dist为到最近的左儿子或右儿子为空的儿子节点的距离+1。
而左偏堆则是左儿子的dist永远大于右儿子的dist。
-
合并函数merge(x,y)
合并第\(x\)个数和第\(y\)个数所在的左偏树
-
删除函数del(x)
删除第\(x\)个数所在的左偏树的根,并合并左右儿子。
不过要注意的是,由于对fa数组进行了路径压缩,所以要把原来的根父亲设为现在的根,使得可以指到新根。
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; void read(int &sum) { sum=0;char last='w',ch=getchar(); while (ch<'0' || ch>'9') last=ch,ch=getchar(); while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar(); if (last=='-') sum=-sum; } void write(int sum) { if (sum<0) printf("-"),sum=-sum; if (sum==0) { putchar('0'),putchar(' '); return ; } char ch[20];int len=0; while (sum>0) ch[++len]=sum%10+'0',sum/=10; for (int i=len;i>=1;i--) putchar(ch[i]); putchar(' '); } struct LOS { int n; int fa[100010]; int c[100010][2]; int nm[100010]; int dst[100010]; void init() { for (int i=1;i<=n;i++) fa[i]=i; for (int i=1;i<=n;i++) dst[i]=1; for (int i=1;i<=n;i++) read(nm[i]); } int findfa(int x) { if (x==fa[x]) return x; else return fa[x]=findfa(fa[x]); } void merge(int x,int y) { if (nm[x]>nm[y] || (nm[x]==nm[y] && x>y)) swap(x,y); // printf("%d %d\n",nm[x],nm[y]); if (c[x][1]==0) { c[x][1]=y; fa[y]=x; } else { merge(c[x][1],y); if (nm[c[x][1]]<=nm[y]) c[x][1]=c[x][1],fa[c[x][1]]=x; else c[x][1]=y,fa[y]=x; } dst[x]=min(dst[c[x][0]],dst[c[x][1]])+1; if (dst[c[x][0]]<dst[c[x][1]]) swap(c[x][0],c[x][1]); } void del(int x) { x=findfa(x); fa[c[x][0]]=c[x][0]; fa[c[x][1]]=c[x][1]; nm[x]=dst[x]=0; fa[x]=0; int l=c[x][0],r=c[x][1]; c[x][0]=c[x][1]=0; if (l==0 && r==0) return; if (l==0) fa[x]=r; if (r==0) fa[x]=l; if (l!=0 && r!=0) { merge(l,r); if (nm[l]>nm[r] || (nm[l]==nm[r] && l>r)) fa[x]=r; else fa[x]=l; } } int ask(int x) { return nm[findfa(x)]; } void write() { for (int i=1;i<=n;i++) { printf("point : %d %d %d %d\n",nm[i],nm[fa[i]],nm[c[i][0]],nm[c[i][1]]); } } }tr; int n,m; int main() { // freopen("P3377_5.in","r",stdin); // freopen("M.out","w",stdout); read(tr.n),read(m); tr.init(); for (int i=1;i<=m;i++) { int t;read(t); if (t==1) { int x,y;read(x),read(y); // printf("%d %d %d\n",t,x,y); if (tr.findfa(x)!=tr.findfa(y) && tr.dst[x]!=0 && tr.dst[y]!=0) tr.merge(tr.findfa(x),tr.findfa(y)); // else printf("-2 %d %d %d %d\n",tr.findfa(x),tr.findfa(y),tr.dst[x],tr.dst[y]); } if (t==2) { int x;read(x); // printf("%d %d\n",t,x); if (tr.dst[x]!=0) { printf("%d\n",tr.ask(x)); tr.del(x); } else printf("-1\n"); } // tr.write(); } // fclose(stdin); // fclose(stdout); return 0; }
平衡树
应用
- \(O(\log_2n)\)查询所有数中第\(x\)大的树
- \(O(\log_2n)\)查询\(x\)数的排名
- \(O(\log_2n)\)删除\(x\)数
- \(O(\log_2n)\)插入\(x\)数
- \(O(\log_2n)\)查询\(x\)数的前驱
- \(O(\log_2n)\)查询\(x\)数的后继
实现
splay
splay在二叉搜索树的基础上,用左旋和右旋两种操作使得树平衡,保证每次操作复杂度都为\(O(\log_2 n)\)
旋转操作
如图,可以用类似的方法完成旋转,不过要注意,当\(x\),\(y\)同为左儿子或右儿子时,要先旋转\(y\),在旋转\(x\)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void writes(char s[],int len) { for (int i=1;i<=len;i++) printf("%c",s[i]); }
int n;
int rt,tot,fa[100100],ch[100100][2],val[100100],cnt[100100],sz[100100];
struct Splay
{
void maintain(int x) { sz[x]=sz[ch[x][0]]+sz[ch[x][1]]+cnt[x]; }
void clear(int x) { fa[x]=ch[x][0]=ch[x][1]=val[x]=cnt[x]=sz[x]=0; }
bool get(int x) { return x==ch[fa[x]][1]; }
void rotate(int x)
{
int y=fa[x],z=fa[y],chk=get(x),bz=get(y);
ch[y][chk]=ch[x][chk^1];fa[ch[x][chk^1]]=y;
ch[x][chk^1]=y;fa[y]=x;
fa[x]=z;if (z!=0) ch[z][bz]=x;
maintain(y),maintain(x);
}
void play(int x)
{
for (int f=fa[x];f=fa[x],f;rotate(x))
if (fa[f]!=0) rotate(get(x)==get(f) ? f : x);
rt=x;
}
void ins(int k)
{
if (rt==0)
{
tot++;val[tot]=k;cnt[tot]++;rt=tot;
fa[tot]=0; maintain(rt); return ;
}
int cnr=rt,f=0;
while (1)
{
if (val[cnr]==k)
{
cnt[cnr]++;
maintain(cnr);maintain(f);
play(cnr);break;
}
f=cnr;cnr=ch[f][val[cnr]<k];
if (cnr==0)
{
tot++;val[tot]=k;cnt[tot]++;
fa[tot]=f;ch[f][val[f]<k]=tot;
maintain(tot);maintain(f);
play(tot); break;
}
}
}
int rk(int k)
{
int res=0,cnr=rt;
while (1)
{
if (k<val[cnr]) cnr=ch[cnr][0];
else
{
res+=sz[ch[cnr][0]];
if (k==val[cnr]) { play(cnr); return res+1; }
res+=cnt[cnr];cnr=ch[cnr][1];
}
}
}
int bth(int k)
{
int cnr=rt;
while (1)
{
if (ch[cnr][0]!=0 && k<=sz[ch[cnr][0]]) cnr=ch[cnr][0];
else
{
k-=cnt[cnr]+sz[ch[cnr][0]];
if (k<=0) { play(cnr); return val[cnr]; }
cnr=ch[cnr][1];
}
}
}
int pre()
{
int cnr=ch[rt][0];
while (ch[cnr][1]>0) cnr=ch[cnr][1];
play(cnr);return cnr;
}
int nxt()
{
int cnr=ch[rt][1];
while (ch[cnr][0]>0) cnr=ch[cnr][0];
play(cnr);return cnr;
}
void del(int k)
{
rk(k);
if (cnt[rt]>1) { cnt[rt]--; maintain(rt); return ; }
if (ch[rt][0]==0 && ch[rt][1]==0) { clear(rt); rt=0; return ; }
if (ch[rt][0]==0) { int cnr=rt; rt=ch[rt][1]; fa[rt]=0; clear(cnr); return ; }
if (ch[rt][1]==0) { int cnr=rt; rt=ch[rt][0]; fa[rt]=0; clear(cnr); return ; }
int cnr=rt,x=pre();
fa[ch[cnr][1]]=x;ch[x][1]=ch[cnr][1];
clear(cnr);maintain(rt);
}
}tree;
signed main()
{
freopen("Splay.in","r",stdin);
freopen("Splay.out","w",stdout);
memset(fa,0,sizeof(fa));memset(ch,0,sizeof(ch));
memset(val,0,sizeof(val));memset(cnt,0,sizeof(cnt));
memset(sz,0,sizeof(sz));
read(n);
for (int i=1;i<=n;i++)
{
int t,x;read(t),read(x);
if (t==1) tree.ins(x);
if (t==2) tree.del(x);
if (t==3) printf("%lld\n",tree.rk(x));
if (t==4) printf("%lld\n",tree.bth(x));
if (t==5) tree.ins(x),printf("%lld\n",val[tree.pre()]),tree.del(x);
if (t==6) tree.ins(x),printf("%lld\n",val[tree.nxt()]),tree.del(x);
}
fclose(stdin);fclose(stdout);
return 0;
}
treap
treap是二叉搜索树和堆的结合,对于每一个数\(x\)都给一个随机的优先级\(p\),要保证对于一个节点,所有的儿子的\(p\)都要大于他的\(p\)
treap有有旋和无旋两种,这里用了无旋。
有分裂和合并两个函数,合并和左偏树一样
说一下分裂,splity(x,val,a,b)
分裂根为\(x\)的树,小于等于val属于\(a\)树,大于val属于\(b\)树。
一般用于实现可持久化平衡树
#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void write(int sum)
{
if (sum<0) printf("-"),sum=-sum;
if (sum==0) { putchar('0'),putchar(' '); return ; }
char ch[20];int len=0;
while (sum>0) ch[++len]=sum%10+'0',sum/=10;
for (int i=len;i>=1;i--) putchar(ch[i]);
putchar(' ');
}
struct Persistence_treap
{
int tm,cnt;
int rt[500010*64];
struct point
{
int l,r;
int w,p;
int sz;
}t[500010*64];
void pushup(int x) { t[x].sz=t[t[x].l].sz+t[t[x].r].sz+1; }
int makenw(int val) { cnt++; t[cnt]={0,0,val,rand(),1}; return cnt; }
void split(int x,int val,int &a,int &b)
{
if (!x) { a=b=0; return ; }
int y=x;x=++cnt;t[x]=t[y];
if (t[x].w<=val) { a=x; split(t[x].r,val,t[x].r,b); }
else { b=x; split(t[x].l,val,a,t[x].l); }
pushup(x);
}
int merge(int x,int y)
{
if (!x || !y) return x+y;
int s=++cnt;
if (t[x].p<t[y].p) { t[s]=t[x]; t[s].r=merge(t[s].r,y); }
else { t[s]=t[y]; t[s].l=merge(x,t[s].l); }
pushup(s);
return s;
}
void ins(int ti,int val)
{
int a,b;
split(rt[ti],val,a,b);
rt[tm]=merge(merge(a,makenw(val)),b);
}
void del(int ti,int val)
{
int a,b,c;
split(rt[ti],val,a,c);
split(a,val-1,a,b);
b=merge(t[b].l,t[b].r);
rt[tm]=merge(merge(a,b),c);
}
int getrnk(int ti,int val)
{
int a,b;
split(rt[ti],val-1,a,b);
return t[a].sz+1;
}
int getbth(int x,int val)
{
if (t[t[x].l].sz+1==val) return t[x].w;
if (t[t[x].l].sz+1>=val) return getbth(t[x].l,val);
else return getbth(t[x].r,val-t[t[x].l].sz-1);
}
int getpre(int ti,int val)
{
int a,b;
split(rt[ti],val-1,a,b);
return getbth(a,t[a].sz);
}
int getnxt(int ti,int val)
{
int a,b;
split(rt[ti],val,a,b);
return getbth(b,1);
}
}tr;
int n;
int main()
{
srand(time(0));
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n);
for (tr.tm=1;tr.tm<=n;tr.tm++)
{
int h,op,val;
read(h),read(op),read(val);
switch (op)
{
case 1 : tr.ins(h,val); break;
case 2 : tr.del(h,val); break;
case 3 : printf("%d\n",tr.getrnk(h,val)); break;
case 4 : printf("%d\n",tr.getbth(tr.rt[h],val)); break;
case 5 : printf("%d\n",tr.getpre(h,val)); break;
case 6 : printf("%d\n",tr.getnxt(h,val)); break;
}
if (op>2) tr.rt[tr.tm]=tr.rt[h];
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
pbds
c++STL,建议在考场上打。
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
int n;
tree<double,null_type,less<double>,rb_tree_tag,tree_order_statistics_node_update> T;
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
read(n);
for (int i=1;i<=n;i++)
{
int t,x;cin >> t >> x;
if (t==1) T.insert(x+i*1e-6);
if (t==2) T.erase(T.lower_bound(x));
if (t==3) printf("%d\n",(int)T.order_of_key(x)+1);
if (t==4) printf("%d\n",(int)*T.find_by_order(x-1));
if (t==5) printf("%d\n",(int)round(*(--T.lower_bound(x))));
if (t==6) printf("%d\n",(int)round(*T.lower_bound(x+1)));
}
// fclose(stdin);fclose(stdout);
return 0;
}
可持久化数据结构
应用
用于记录历史区间的信息,并动态查询修改
思想都是一样的,在每次修改的路径上都新建一个点,记录新的信息,连到旧的节点上。
内容
- 可持久化线段树
- 可持久化平衡树
- 可持久化并查集(可能叫主席树?)
- 可持久化trie树
- 可持久化左偏树(优化\(k\)短路)
AC自动机
应用
查询模式串\(S\)在文本串\(T\)出现的次数
实现
也是有一个KMP的失配数组,失配了就转移到另一个上面。
不过有一个拓扑优化。
类似路径压缩一样,可以利用之前的查询,直接得到出现的次数。
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
void read(int &sum)
{
sum=0;char last='w',ch=getchar();
while (ch<'0' || ch>'9') last=ch,ch=getchar();
while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
if (last=='-') sum=-sum;
}
void write(int sum)
{
if (sum<0) printf("-"),sum=-sum;
if (sum==0) { putchar('0'),putchar(' '); return ; }
char ch[20];int len=0;
while (sum>0) ch[++len]=sum%10+'0',sum/=10;
for (int i=len;i>=1;i--) putchar(ch[i]);
putchar(' ');
}
int n,m;
char s[2000010];
int ans[200010];
struct tree
{
int cnt;
int p[200010][26];
int f[200010][2];
int sum[200010];
int fail[200010];
int rd[200010];
int bz[200010];
void init()
{
cnt=0;
memset(p,0,sizeof(p));
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
memset(fail,0,sizeof(fail));
}
void add(char *s,int sb)
{
int now=0,x=0;
while (s[x])
{
if (p[now][s[x]-'a']==0) p[now][s[x]-'a']=++cnt,f[cnt][0]=now,f[cnt][1]=s[x]-'a';
now=p[now][s[x]-'a'];
x++;
}
sum[now]++;
ans[sb]=now;
}
void build()
{
queue<int> q;while (q.empty()==false) q.pop();
for (int i=0;i<26;i++) if (p[0][i]) fail[p[0][i]]=0,q.push(p[0][i]);
while (!q.empty())
{
int now=q.front();q.pop();
for (int i=0;i<26;i++)
if (p[now][i]) fail[p[now][i]]=p[fail[now]][i],
q.push(p[now][i]),
rd[p[fail[now]][i]]++;
else p[now][i]=p[fail[now]][i];
}
}
void ask(char *s)
{
memset(bz,0,sizeof(bz));
int now=0,x=0;
while (s[x])
{
now=p[now][s[x]-'a'];
bz[now]++;
x++;
}
queue<int> q;
for (int i=0;i<=cnt;i++)
{
if (rd[i]==0)
q.push(i);
}
while (!q.empty())
{
now=q.front();q.pop();
int y=fail[now];
rd[y]--;
bz[y]+=bz[now];
if (rd[y]==0) q.push(y);
}
}
}tr;
int main()
{
// freopen("M.in","r",stdin);
// freopen("M.out","w",stdout);
tr.init();
cin >> n;
for (int i=1;i<=n;i++)
{
cin >> s;
// printf("%s\n",s);
tr.add(s,i);
}
scanf("%s",s);
tr.build();
tr.ask(s);
for (int i=1;i<=n;i++)
printf("%d\n",tr.bz[ans[i]]);
// fclose(stdin);
// fclose(stdout);
return 0;
}




浙公网安备 33010602011771号