zoukankan      html  css  js  c++  java
  • (模板)luoguP3806(树上点分治模板题)

    点分治的写法1:

    题目链接:https://www.luogu.org/problem/P3806

    题意:给出一颗带边权的树,结点数n<=1e4,每条边有权值<=1e4,有m组询问(m<=100),每组询问为一个k,表示是否存在一条路经长度为k,存在输出AYE,不存在输出NAY。

    思路:点分治模板题,第一次学点分治。这位聚聚的讲解特别好,安利一波:https://blog.csdn.net/a_forever_dream/article/details/81778649。

       写法1:先计算所有组合情况,然后删除全部在子树中的路径,再递归求解。这种写法一般在统计答案时要排序,根据单调型或二分查找。常数比写法二要大。

    AC代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int inf=0x3f3f3f3f;
    const int maxn=10005;
    struct node1{
        int v,w,nex;
    }edge[maxn<<1];
    
    struct node2{
        int x,y;
    }arr[maxn];
    
    int n,m,cnt,size;
    int head[maxn],root,Min,sz[maxn],mson[maxn],vis[maxn];
    int t,tt,dis[maxn],ask[105],ans[105];
    
    void adde(int u,int v,int w){
        edge[++cnt].v=v;
        edge[cnt].w=w;
        edge[cnt].nex=head[u];
        head[u]=cnt;
    }
    
    void getroot(int u,int fa){   
        sz[u]=1,mson[u]=0;    //mson记录最大子树的大小
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]||v==fa) continue; //vis记录是否分治过
            getroot(v,u);
            sz[u]+=sz[v];
            if(sz[v]>mson[u]) mson[u]=sz[v];
        }
        if(size-sz[u]>mson[u]) mson[u]=size-sz[u];
        if(Min>mson[u]) Min=mson[u],root=u;
    }
    
    void getdis(int u,int fa,int len){  //len表示u到目标点的距离
        dis[++t]=len;
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(v==fa||vis[v]) continue;
            getdis(v,u,len+edge[i].w);
        }
    }
    
    void solve(int x,int y,int f){  //x是geidis的起点,y是x到目标点的距离,f表示合法还是不合法
        t=0;       
        getdis(x,0,y);     //算出树中点到目标点的距离
        tt=0;
        sort(dis+1,dis+t+1);
        dis[0]=-1;
        for(int i=1;i<=t;++i) //把距离相等的整合在一起,方便处理
            if(dis[i]!=dis[i-1]) arr[++tt].x=dis[i],arr[tt].y=1;
            else ++arr[tt].y;
        for(int i=1;i<=m;++i){
            if(ask[i]%2==0)   //单独处理到根的距离为k/2的点,它们不需二分
                for(int j=1;j<=tt;++j)
                    if(arr[j].x==ask[i]/2) 
                        ans[i]+=(arr[j].y-1)*arr[j].y/2*f;
            for(int j=1;j<=tt&&arr[j].x<ask[i]/2;++j){ //仅枚举小于k/2的
                int l=j+1,r=tt,mid;
                while(l<=r){
                    mid=(l+r)>>1;
                    if(arr[j].x+arr[mid].x==ask[i]){
                        ans[i]+=arr[j].y*arr[mid].y*f;
                        break;
                    }
                    if(arr[j].x+arr[mid].x>ask[i]) r=mid-1;
                    else l=mid+1;
                }
            }
        }
    }
    
    void fenzhi(int u,int ssize){    //ssize是当前这颗子树的大小
        vis[u]=1;
        solve(u,0,1);          //计算这颗树以u为重心的所有组合
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]) continue;
            solve(v,edge[i].w,-1); //减去不合法组合
            Min=inf,root=0;
            size=sz[v]<sz[u]?sz[v]:(ssize-sz[u]);
            getroot(v,0);
            fenzhi(root,size);
        }
    }
    
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=1;i<n;++i){
            int u,v,w;    
            scanf("%d%d%d",&u,&v,&w);
            adde(u,v,w);
            adde(v,u,w);
        }
        for(int i=1;i<=m;++i)
            scanf("%d",&ask[i]);
        root=0,Min=inf,size=n;
        getroot(1,0);
        fenzhi(root,n);
        for(int i=1;i<=m;++i)
            if(ans[i]>0) printf("AYE
    ");
            else printf("NAY
    ");
        return 0;
    }

    点分治写法2:

    题目链接:https://www.luogu.org/problem/P4149

    题意:给定一颗树,求路径长为k的边数最小的路径,求最小边数。

    思路:点分治裸体。写法2按子树依次递归进入,进入后先统计答案,再统计相关值,一般用桶来统计。统计答案用到了前面子树的信息。写法2的常数小于写法1。

    AC代码:

    #include<cstdio>
    #include<algorithm>
    #include<cctype>
    using namespace std;
    
    const int maxn=200005;
    const int maxk=1000005;
    const int inf=0x3f3f3f3f;
    
    inline int read(){
        int x=0,f=0;char c=0;
        while(!isdigit(c)) {f|=c=='-';c=getchar();}
        while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return f?-x:x;
    }
    
    struct node{
        int v,w,nex;
    }edge[maxn<<1];
    
    int n,ans=inf,cnt,k,head[maxn],root,size,Min,sz[maxn],mson[maxn];
    int vis[maxn],mine[maxk],dis1[maxn],dis2[maxn],t,tt;
    
    void adde(int u,int v,int w){
        edge[++cnt].v=v;
        edge[cnt].w=w;
        edge[cnt].nex=head[u];
        head[u]=cnt;
    }
    
    void getroot(int u,int fa){
        sz[u]=1,mson[u]=0;
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]||v==fa) continue;
            getroot(v,u);
            sz[u]+=sz[v];
            if(sz[v]>mson[u]) mson[u]=sz[v];
        }
        if(size-sz[u]>mson[u]) mson[u]=size-sz[u];
        if(mson[u]<Min) Min=mson[u],root=u;
    }
    
    void getdis(int u,int fa,int d1,int d2){
        if(d1>k) return;
        dis1[++t]=d1,dis2[t]=d2;
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]||v==fa) continue;
            getdis(v,u,d1+edge[i].w,d2+1);
        }
    }
    
    void solve(int u){
        mine[0]=0,t=0;  //考虑一个端点是u的情况
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]) continue;
            tt=t;
            getdis(v,u,edge[i].w,1);
            for(int j=tt+1;j<=t;++j)  //更新答案
                ans=min(ans,mine[k-dis1[j]]+dis2[j]);
            for(int j=tt+1;j<=t;++j)  //更新桶
                mine[dis1[j]]=min(mine[dis1[j]],dis2[j]);
        }
        for(int i=1;i<=t;++i)
            mine[dis1[i]]=inf;
    }
    
    void fenzhi(int u,int ssize){
        vis[u]=1;
        solve(u);
        for(int i=head[u];i;i=edge[i].nex){
            int v=edge[i].v;
            if(vis[v]) continue;
            Min=inf,root=0;
            size=sz[v]<sz[u]?sz[v]:(ssize-sz[u]);
            getroot(v,0);
            fenzhi(root,size);
        }
    }
    
    int main(){
        n=read(),k=read();
        for(int i=1;i<n;++i){
            int u=read()+1,v=read()+1,w=read();
            adde(u,v,w);
            adde(v,u,w);
        }
        Min=inf,root=0,size=n;
        getroot(1,0);
        for(int i=0;i<=k;++i)
            mine[i]=inf;
        fenzhi(root,n);
        printf("%d
    ",ans<n?ans:-1);
        return 0;
    }
  • 相关阅读:
    关于js中event的target和currentTarget的区别
    linux下安装tomcat
    Linux下安装配置JDK
    上传bash: rz: command not found
    Linux忘记 root密码的解决办法
    Linux的关机与重启命令
    windows版本jenkins部署django项目
    Jenkins部署邮件通知
    Jenkins+Ant+Jmeter接口自动化集成测试实例
    postman+jenkins集成
  • 原文地址:https://www.cnblogs.com/FrankChen831X/p/11381526.html
Copyright © 2011-2022 走看看