(模板)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;
}
原文地址:https://www.cnblogs.com/FrankChen831X/p/11381526.html