初三暑假题表重点

初三暑假题表重点复习

记忆

  1. 后缀数组
  2. 动态树
  3. AC机
  4. 树状数组
  5. pbds
  6. 全源最短路
  7. 网络流

并查集

种类并查集

  1. 过程

    有多少个种类就开多少个并查集数组

    每个并查集内部的连边表示\(x,y\)是同一种类,与外部并查集的连边表示\(x,y\)不是同一种类

    这样就可以快速分别\(x,y\)是不是同一类

    jqYPSJ.png

    如图,这就是POJ1182中,当\(1,3\)同类,\(2\)\(4\)\(3\)\(2\)的时候的图

    如果\(x,y\)是同类,那么\(1x,1y\)必须在一个集合里

    如果\(x\)\(y\),那么\(1x,2y\)必须在一个集合里

  2. 复杂度

    \(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;
    }
    

带权并查集

  1. 过程

    只开一个并查集,在每一次找父亲的时候维护一个关系数组,不过这个关系要满足可传递性

  2. 复杂度

    空间\(O(n)\),每次查询修改时间\(O(\log_en)\)

三维偏序

  1. 过程

    首先,按\(x\)从大到小排序,之后进行归并排序。

    对于一个区间\([l,r]\),树状数组记录每个已经排好序的点的\(y\)出现了多少次,既\(sum(1,i)=\),已经排好序的点的\(y\)小于等于\(i\)的个数

    让以\(z\)为关键字,对\([l,r]\)排序,对于每个逆序对(要求一个在\([l,mid]\),一个在\([mid+1,r]\)),这个点的\(ans\)就加上树状数组中,小于等于它的y的个数

  2. 复杂度

    空间\(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;
    }
    
    
    

树状数组

一维树状数组

  1. 单点修改,区间查询

  2. 区间修改,单点查询

    对原数组进行差分,即\(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)\)

  3. 区间修改,区间查询

    这个问题,可以在\(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;
    }
    
    
    

二维树状数组

  1. 单点修改,区间查询

  2. 区间修改,单点查询

    同上,不过差分要这样做

    \[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\)

  3. 区间修改,区间查询

    也是同上,思考如今计算

    \[\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;
}

应用

  1. 任意两个子串的LCP

    \[LCP(sa[i],sa[j])=\min_{k=i+1}^{i}h[k] \]

  2. 不同子串的数目

    \[sum=\sum_{i=1}^{n}h[i] \]

  3. 出现至少 \(k\) 次的子串的最大长度

    二分子串的长度,看看有没有连续的\(h[i]\)大于等于子串长度,且连续长度大于等于\(k\)

  4. 是否有某字符串在文本串中至少不重叠地出现了两次

    同样二分子串长度,看看有没有连续\(h[i]\)大于等于子串长度,且不相交。

树分治

当要处理大规模的树上路径信息问题,可以考虑点分,或边分。

点分治

过程

  1. 找重心
  2. 统计儿子信息
  3. 信息合并
  4. 递归儿子

复杂度

因为是重心,所以最多有\(\log_2n\)层,每个点都要递归一遍,所以\(O(n\log_2^2n)\)

边分治

过程

  1. 把树变为二叉树如图

  2. 找到重边

  3. 统计儿子信息

  4. 信息合并

  5. 递归儿子

复杂度

因为变为二叉树,最多增加\(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方法

  1. \(a\)中的元素离散化
  2. \(f[i]\)表示以\(a[i]\)结尾的LIS的长度,\(t[i]\)表示后面的元素的位置
  3. 用线段树查询\(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)\)

  1. \(S=S_1+'`'+S_2\)
  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)\)

  1. 设字符串为\(S\),首先我们要寻找它的对称中心,但发现对称中心可能在一个字符上,也可能在两个字符中间

    如图

    因为在两个字符中间的我们不好访问,于是我们可以在中间插入一些无关的字符,让字符来代替中间的空隙在这里插入图片描述

  2. 我们记录一个数组\(P\)\(P_i\)表示以\(S_i\)为中心的回文数列的最大半径

    在这里插入图片描述

    如图,\(P_7=6\),在记录\(mid,r\),表示当前已知的\(P_i\)中,\(P_{mid}+r\)是最大的,即右区间离\(S_{S.length()}\)最近

  3. 于是对于一个未知的\(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)\)

  4. 但是我们不知道\(mid+r\)后面是否还有相同的\(P_x,P_y\),所以要暴力枚举,最后更新\(mid,r\)

  5. 于是最大值就是\(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式

\[f[l][r]=\max_{k=l}^{r-1}f[l][k]+f[k+1][r]+w(l,r) \]

如果\(\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,第三层循环减少了,可以证明

\[\huge \sum_{1\leq l<r\leq n} m_{l+1,r} - m_{l,r-1} = \sum_{i=1}^n m_{i,n} - m_{1,i}\leq n^2 \]

左偏树(可并堆)

应用

可以\(O(\log_2 n+\log_2 m)\)堆合并,还可以优化某些题(例如\(k\)短路就需要用到可持久化左偏树)

实现

定义一个点的dist为到最近的左儿子或右儿子为空的儿子节点的距离+1。

而左偏堆则是左儿子的dist永远大于右儿子的dist。

  1. 合并函数merge(x,y)

    合并第\(x\)个数和第\(y\)个数所在的左偏树

    vJhpse.md.png

  2. 删除函数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;
    }
    

平衡树

应用

  1. \(O(\log_2n)\)查询所有数中第\(x\)大的树
  2. \(O(\log_2n)\)查询\(x\)数的排名
  3. \(O(\log_2n)\)删除\(x\)
  4. \(O(\log_2n)\)插入\(x\)
  5. \(O(\log_2n)\)查询\(x\)数的前驱
  6. \(O(\log_2n)\)查询\(x\)数的后继

实现

splay

splay在二叉搜索树的基础上,用左旋和右旋两种操作使得树平衡,保证每次操作复杂度都为\(O(\log_2 n)\)

旋转操作

vJ4IHJ.md.png

如图,可以用类似的方法完成旋转,不过要注意,当\(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\)树。

vJoqCn.md.png

一般用于实现可持久化平衡树

#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;
}

可持久化数据结构

应用

用于记录历史区间的信息,并动态查询修改

思想都是一样的,在每次修改的路径上都新建一个点,记录新的信息,连到旧的节点上。

内容

  1. 可持久化线段树
  2. 可持久化平衡树
  3. 可持久化并查集(可能叫主席树?)
  4. 可持久化trie树
  5. 可持久化左偏树(优化\(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;
}
posted @ 2022-07-22 00:55  WBWYX  阅读(50)  评论(0)    收藏  举报