zoukankan      html  css  js  c++  java
  • 点分治学习笔记+题目汇总

     

    好消息,今天我学会了点分治。所谓点分治,就是将树上的需要点统计的N^2的问题强行转化为NlogN。

    本来事情是这样的:

     1 inline void dfs(int x,int fa){
     2     sz[x]=1;
     3     for(int i=beg[x];i;i=nex[i]){
     4         int t=to[i];
     5         ………… 
     6         //统计这个子树,复杂度是这个子树的大小 
     7         dfs(t,x); 
     8         sz[x]+=sz[t]; 
     9     }
    10 }

    一旦树退化成链,这样遍历就是N^2的复杂度。遍历的时间效率取决于子树的大小。

    于是便想到了重心。

    重心的定义:树上的一个点使其所有子树大小的最大值最小。

    由平均值原理已知其最大的子数大小也小于等于N/2,这样便保证了稳定的logN。

    具体实现如下:

    inline void getrt(int x,int fa){//找重心 
        sz[x]=1;son[x]=0;
        //sz数组记录这个子树的大小,son数组记录其最大子树的大小。
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t]||t==fa)continue;
            //vis数组用于判断这个点是否到过。
            getrt(t,x);
            sz[x]+=sz[t];
            if(sz[t]>son[x])son[x]=sz[t]; 
        }
        if(son[x]<size-sz[x])son[x]=size-sz[x];
        if(mx>son[x])mx=son[x],rt=x;
    }
    inline void solve(int x){
        vis[x]=1;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            …………
            //子树大小的复杂度来处理
            mx=inf,rt=0,size=sz[t];
            getrt(t,x);
            //找这个子树的重心
            solve(rt);
            //处理这棵子树 
        }
    }

    我们来看一个例题。

    例题1:P2634 [国家集训队]聪聪可可

    网址:https://www.luogu.com.cn/problem/P2634

    对于每个点,统计过他的边的对三取模的个数。看代码。

    #include<bits/stdc++.h>
    using namespace std;
    #define inf 1e9
    const int maxn=20005;
    int beg[maxn],nex[maxn*2],to[maxn*2],w[maxn*2],e;
    inline void add(int x,int y,int z){
        e++;nex[e]=beg[x];
        beg[x]=e;to[e]=y;w[e]=z;
    }
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        while(c>'9'||c<'0'){
            if(c=='-')f=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9'){
            x=(x<<1)+(x<<3)+c-'0';
            c=getchar();
        }
        return x*f;
    }
    int n,mx,rt,size,ans;
    int sz[maxn],son[maxn];
    int vis[maxn],tmp[5],dp[maxn][5];
    inline void getrt(int x,int fa){
        sz[x]=1;son[x]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            getrt(t,x);
            sz[x]+=sz[t];
            if(sz[t]>son[x])son[x]=sz[t];
        }
        if(son[x]<size-sz[x])son[x]=size-sz[x];
        if(son[x]<mx)mx=son[x],rt=x;
    }
    
    inline void calc(int x,int fa,int val){
        tmp[val%3]++;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            calc(t,x,w[i]+val);
        }
    }
    inline void divide(int x){
        vis[x]=1;
        int tp[5];
        tp[0]=tp[1]=tp[2]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            tmp[0]=tmp[1]=tmp[2]=0;
            calc(t,x,w[i]);
            dp[x][0]=dp[x][0]+2*tmp[0]+2*tmp[0]*tp[0]+2*tmp[1]*tp[2]+2*tmp[2]*tp[1];
            dp[x][1]=dp[x][1]+2*tmp[1]+2*tmp[0]*tp[1]+2*tmp[1]*tp[0]+2*tmp[2]*tp[2];
            dp[x][2]=dp[x][2]+2*tmp[2]+2*tmp[0]*tp[2]+2*tmp[1]*tp[1]+2*tmp[2]*tp[0];
            tp[0]+=tmp[0],tp[1]+=tmp[1],tp[2]+=tmp[2];
            mx=inf,rt=0,size=sz[t];
            getrt(t,x);
            divide(rt);
        }
        dp[x][0]++;
        ans+=dp[x][0];
        //printf("%d %d
    ",x,dp[x][0]);
    }
    inline int gcd(int x,int y){
        if(y==0)return x;
        return gcd(y,x%y);
    }
    int main(){
        cin>>n;
        int x,y,z;
        for(int i=1;i<n;i++){
            x=read(),y=read(),z=read();
            add(x,y,z);add(y,x,z);
        }
        mx=inf,rt=0,size=n;
        getrt(1,0);
        divide(rt);
        int t=gcd(ans,n*n);
        printf("%d/%d
    ",ans/t,n*n/t);
        return 0;
    }

    很板子的一道题哈!

     例题2:P3806 【模板】点分治1

    网址:https://www.luogu.com.cn/problem/P3806

    虽然说是一个模板题,但其实并不是那么模板……

    数据加强之后,N^2的时间复杂度是过不了的。注意到m只有一百,考虑先输入m个数,离线处理。

    代码如下:

    #include<bits/stdc++.h>
    using namespace std;
    #define inf 0x3f3f3f3f
    const int maxn=25000;
    inline int read(){
        int x=0,f=1;
        char c=getchar();
        while(c>'9'||c<'0'){
            if(c=='-')f=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9'){
            x=(x<<1)+(x<<3)+c-'0';
            c=getchar();
        }
        return x*f;
    }
    int beg[maxn],nex[maxn],to[maxn],w[maxn],e;
    void add(int x,int y,int z){
        e++;nex[e]=beg[x];
        beg[x]=e;to[e]=y;w[e]=z;
    }
    int n,m;
    int mx,size,rt;
    int sz[maxn],son[maxn];
    int vis[maxn],ans[20000005],top,st[maxn];
    inline void getrt(int x,int fa){
        sz[x]=1;son[x]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            getrt(t,x);
            sz[x]+=sz[t];
            if(sz[t]>son[x])son[x]=sz[t];
        }
        if(size-sz[x]>son[x])son[x]=size-sz[x];
        if(son[x]<mx)mx=son[x],rt=x;
    }
    inline void query(int pos,int fa,int have){
        st[++top]=have;
        for(int i=beg[pos];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            query(t,pos,have+w[i]);
        }
    }
    int q[maxn],p,ask[maxn],test[maxn];
    inline void solve(int pos){
        p=0;ans[0]=1;
        for(int i=beg[pos];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            top=0;
            query(t,pos,w[i]);
            for(int j=top;j;j--)
                for(int k=1;k<=m;k++)
                    if(ask[k]>=st[j])test[k]|=ans[ask[k]-st[j]];
            for(int j=1;j<=top;j++)
                q[++p]=st[j],ans[st[j]]=1;
        }
        for(int i=1;i<=p;i++)
            ans[q[i]]=0;
    }
    inline void devide(int x){
        vis[x]=1;
        solve(x);
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            mx=inf,size=sz[t],rt=0;
            getrt(t,0);
            devide(rt);
        }
    }
    int main(){
        n=read(),m=read();
        int x,y,z;
        for(int i=1;i<n;i++){
            x=read(),y=read(),z=read();
            add(x,y,z);
            add(y,x,z);
        }
        for(int i=1;i<=m;i++)
            ask[i]=read();
        mx=inf,size=n,rt=0;
        getrt(1,0);
        devide(rt);
        for(int i=1;i<=m;i++)
            puts(test[i]?"AYE":"NAY");
        return 0;
    } 

    请注意ans数组要尽量开大一点,不然会RE,QAQ。

    例题3:P4178 Tree

    网址:https://www.luogu.com.cn/problem/P4178

    我个人觉得这道题比例1难比例2水,难点主要是在处理的部分。

    对于每一条连到当前根的比k小的边edge,找比k-edge小的,不难想到树状数组。

    比较坑的是树状数组一定要定义局部变量,不然会奇妙WA哈哈。

    看一下代码:

    #include<bits/stdc++.h>
    using namespace std;
    int n,k;
    const int maxn=100000;
    int beg[maxn],nex[maxn],to[maxn],w[maxn],e;
    void add(int x,int y,int z){
        e++;nex[e]=beg[x];
        beg[x]=e;to[e]=y;w[e]=z;
    }
    #define inf 1e9
    int rt,mx,size;
    int sz[maxn],son[maxn],vis[maxn];
    inline void getrt(int x,int fa){
        sz[x]=1,son[x]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t]||t==fa)continue;
            getrt(t,x);
            sz[x]+=sz[t];
            if(son[x]<sz[t])son[x]=sz[t];
        }
        if(son[x]<size-sz[x])son[x]=size-sz[x];
        if(mx>son[x])mx=son[x],rt=x;
    }
    int q[maxn],ans,top;
    inline void stk(int x,int fa,int val){
        if(val>k)return;
        if(val==k){
            ans++;
            return;
        }
        q[++top]=val;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t]||t==fa)continue;
            stk(t,x,val+w[i]);
        }
    }
    inline int lowbit(int x){
        return x&(-x);
    }
    inline void divide(int x){
        //printf("%d
    ",x);
        vis[x]=1;
        int c[k+10]={0};
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            top=0;
            stk(t,x,w[i]);
            ans+=top;
            //printf("%d %d %d %d ",t,x,top,ans);
            for(int j=1;j<=top;j++){
                int tmp=k-q[j];
                while(tmp){
                    ans+=c[tmp];
                    tmp-=lowbit(tmp);
                }
            }
            //printf("%d
    ",ans);
            for(int j=1;j<=top;j++){
                int tmp=q[j];
                while(tmp<=k){
                    c[tmp]++;
                    tmp+=lowbit(tmp);
                }
            }
            mx=inf,size=sz[t],rt=0;
            getrt(t,x);
            divide(rt);
        }
        //printf("%d %d
    ",qwq,x);
    }
    int main(){
        cin>>n;
        int x,y,z;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);add(y,x,z);
        }
        cin>>k;
        mx=inf,rt=0,size=n;
        getrt(1,0);
        divide(rt);
        printf("%d
    ",ans);
        return 0;
    }

    努力想想的我一遍A了。

    例题4:CF161D Distance in Tree

    网址:https://www.luogu.com.cn/problem/CF161D

    这可以说是最简单的一道了吧,用一个桶去记录就好了。

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100000+10;
    #define inf 1e9
    int n,k;
    int beg[maxn],nex[maxn],to[maxn],w[maxn],e;
    void add(int x,int y,int z){
        e++;nex[e]=beg[x];
        beg[x]=e;to[e]=y;w[e]=z;
    }
    int mx,size,rt;
    int sz[maxn],son[maxn],vis[maxn];
    inline void getrt(int x,int fa){
        sz[x]=1,son[x]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t]||t==fa)continue;
            getrt(t,x);
            sz[x]+=sz[t];
            if(son[x]<sz[t])son[x]=sz[t];
        }
        if(son[x]<size-sz[x])son[x]=size-sz[x];
        if(son[x]<mx)mx=son[x],rt=x;
    }
    int top,q[maxn],ans;
    inline void stk(int x,int fa,int val){
        if(val>k)return;
        if(val==k){
            ans++;
            return;
        }
        q[++top]=val;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            stk(t,x,val+w[i]);
        }
    }
    inline void divide(int x){
        int bot[500+10]={0};
        vis[x]=1;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            top=0;stk(t,x,w[i]);
            for(int i=1;i<=top;i++)
                ans+=bot[k-q[i]];
            for(int i=1;i<=top;i++)
                bot[q[i]]++;
            mx=inf,size=sz[t],rt=0;
            getrt(t,x);
            divide(rt);
        }
    }
    int main(){
        cin>>n>>k;
        int x,y,z;
        for(int i=1;i<n;i++){
            scanf("%d%d",&x,&y);
            add(x,y,1);add(y,x,1);
        }
        mx=inf,size=n,rt=0;
        getrt(1,0);
        divide(rt);
        printf("%d
    ",ans);
        return 0;
    } 

    只要前面学懂了,这题应该要一遍AC,就像我一样^_^

    例题5:P4149 [IOI2011]Race

    转眼间,做了五道了。唉。

    这一题k有1000000的数量级,所以我没敢开一百万的局部数组。

    为什么全局有可能会出问题?因为在递归的时候,这一层的数据有可能被下一层利用。

    避免的方法就是把处理和递归分开。完事!

    看代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1000000+100;
    #define inf 1e9
    int n,k;
    int beg[maxn],nex[maxn],to[maxn],w[maxn],e;
    void add(int x,int y,int z){
        e++;nex[e]=beg[x];
        beg[x]=e;to[e]=y;w[e]=z;
    }
    int mx,rt,size;
    int sz[maxn],son[maxn],vis[maxn];
    inline void getrt(int x,int fa){
        sz[x]=1,son[x]=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            getrt(t,x);
            sz[x]+=sz[t];
            if(son[x]<sz[t])son[x]=sz[t];
        }
        if(son[x]<size-sz[x])son[x]=size-sz[x];
        if(son[x]<mx)mx=son[x],rt=x;
    }
    int bot[maxn],top,q1[maxn],q2[maxn],ans;
    inline void stk(int x,int fa,int val,int edge){
        if(val>k)return;
        if(val==k){
            ans=min(ans,edge);
            return;
        }
        if(edge>=ans)return;
        q1[++top]=val;
        q2[top]=edge;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(t==fa||vis[t])continue;
            stk(t,x,val+w[i],edge+1);
        }
    }
    int tmp[maxn],qwq;
    inline void divide(int x){
        vis[x]=1;qwq=0;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            top=0;
            stk(t,x,w[i],1);
            for(int j=1;j<=top;j++)
                ans=min(ans,q2[j]+bot[k-q1[j]]);
            for(int j=1;j<=top;j++)
                bot[q1[j]]=min(bot[q1[j]],q2[j]);
            for(int j=1;j<=top;j++)
                tmp[++qwq]=q1[j];
        }
        for(int i=1;i<=qwq;i++)
            bot[tmp[i]]=inf;
        for(int i=beg[x];i;i=nex[i]){
            int t=to[i];
            if(vis[t])continue;
            mx=inf,rt=0,size=sz[t];
            getrt(t,x);
            divide(rt);
        }
    }
    int main(){
        cin>>n>>k;
        int x,y,z;
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&x,&y,&z);
            x++,y++;
            add(x,y,z),add(y,x,z);
        }
        for(int i=1;i<=k;i++)
            bot[i]=1e9;
        ans=inf;
        mx=inf,rt=0,size=n;
        getrt(1,0);
        divide(rt);
        if(ans>=n)puts("-1");
        else printf("%d
    ",ans);
        return 0;
    } 

    不巧,我又是一遍AC的,哈哈哈!

    例题6:HDU 4812 D Tree  未完成

    例题7:P3060 [USACO12NOV]平衡树Balanced Trees  未完成

    例题8:P3727 曼哈顿计划E  未完成

    例题9:P3345 [ZJOI2015]幻想乡战略游戏  未完成

    例题10:P4886 快递员  未完成

    例题11:P3920 [WC2014]紫荆花之恋  未完成

    例题12:P5306 [COCI2019] Transport  未完成

    例题13:P3714 [BJOI2017]树的难题  未完成

  • 相关阅读:
    新克隆环境无法创建供应商,报供应商名称已存在
    批处理学习:for语句详解【经典】(转)
    信号与槽引用传递
    串口封装
    tcp客户端封装
    qt无法定位程序输入点 于动态链接库 qt5core.dll
    对象new和不new的理解
    Qt重绘机制
    红绿灯
    获取所有子控件
  • 原文地址:https://www.cnblogs.com/syzf2222/p/12274688.html
Copyright © 2011-2022 走看看