POJ 1741 & BZOJ 1468 点分治

http://www.elijahqi.win/2018/01/15/poj-1741-bzoj-1468/
Description
给你一棵TREE,以及这棵树上边的距离.问有多少对点它们两者间的距离小于等于K
Input
N(n<=40000) 接下来n-1行边描述管道,按照题目中写的输入 接下来是k
Output
一行,有多少对点之间的距离小于等于k
Sample Input
7
1 6 13
6 3 9
3 5 7
4 1 3
2 4 20
4 7 2
10
Sample Output
5
HINT

Source

LTC男人八题系列

bzoj题意已经非常清晰明了了 今天刚学点分治 做一下论文里的第一道题

基于点的分治:
首先选取一个点将无根树转为有根树,再递归处理每一颗以根结
点的儿子为根的子树。
根据论文中的提示 我们要求出的是dis[i]+dis[j]<=k的(i,j)的个数 设dis为i到根节点的距离belong(i)=x表示x为根节点的某个儿子那么我想要的答案可以通过容斥统计出来 那就是dis[i]+dis[j]<=k且belong[i]!=belong[j]的个数=dis[i]+dis[j]<=k的总的个数-dis[i]+dis[j]<=k belong[i]=belong[j]的个数那么怎么搞 分治的重点是我需要找一个当前子树的重心来搞 何为中心就是子树中最大的子树的数量最小的节点 那么我们 就可以递归去找了每次找重心的时候先递归算一下我的子树都有多大 然后和我当前子树的最大值比较一下 然后注意还有可能存在我的子树是我父节点那个树的情况 那就是我还要再比较一下(节点数-我这个点以下的总点数) 注意这个节点数是随着分治的子树不同在改变的 那么对于本题怎么搞答案 我首先可以o(n)遍历我子树内的所有点 然后算出他们到根节点的距离和 然后针对这个数组排序 设定l,r两个指针 当一开始l< r时且他们满足条件 那么显然l+1~r这些都可以和l配对 答案增加r-l 如果不满足上面的等式了 计算完之后将l右移如果不满足条件了则一直左移r知道满足条件 综上实现o(n)求距离<=k的点对的情况 然后这时候我只需要再分别计算下我子树里满足情况的个数一减即可 具体实现细节看蒟蒻qwq我的代码
这里写图片描述
对于这个点分一开始写的方式存在一个问题 感谢zhx巨佬提醒%%%%%%%%%%%%% 我每次solve(root) 我每次做 的时候去get_root的时候 存在一个问题首先我可以确定我这个当前子树的中心是start下面的那个entry (没仔细qwq算谁是中心 那么来看这时候我size(start) 的范围显然是不对的 因为我明明这个子树只是1 但是我这样去算子树的根的时候的时候重心会找错 可能时间复杂度会有问题 所以我每次在做这个子树的时候我先dfs一下把size重新更新一下 然后 再去找重心即可

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 11000
#define inf 0x3f3f3f3f
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0;char ch=gc();
    while(ch<'0'||ch>'9') ch=gc();
    while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=gc();
    return x;
}
struct node{
    int y,z,next;
}data[N<<1];
int n,ans,num,sum,d[N],dis[N],f[N],size[N],root,h[N],k;bool visit[N];
inline void get_root(int x,int fa){
    f[x]=0;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;
        if (visit[y]||y==fa) continue;
        get_root(y,x);f[x]=max(f[x],size[y]);
    }f[x]=max(f[x],sum-size[x]);
    if (f[root]>f[x]) root=x;
}
inline void get_dis(int x,int fa){
    dis[++num]=d[x];
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;
        if (y==fa||visit[y]) continue;
        d[y]=d[x]+z;get_dis(y,x);
    }
}
inline int calc(int x,int cost){
    num=0;int tmp=0;d[x]=cost;get_dis(x,0);
    sort(dis+1,dis+num+1);int l=1,r=num;
    while(l<r){
        if (dis[l]+dis[r]<=k){
            tmp+=r-l;++l;
        }else --r;
    }return tmp;
}
inline void get_size(int x,int fa){
    size[x]=1;
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y;if (y==fa||visit[y]) continue;
        get_size(y,x);size[x]+=size[y];
    }
}
inline void solve(int x){
    visit[x]=1;ans+=calc(x,0);get_size(x,0);
    for (int i=h[x];i;i=data[i].next){
        int y=data[i].y,z=data[i].z;if (visit[y]) continue;
        ans-=calc(y,z);sum=size[y];root=0;get_root(y,x);solve(root);
    }
}
int main(){
    freopen("poj1741.in","r",stdin);
    while(1){
        n=read();k=read();if (!n&&!k) return 0;memset(visit,0,sizeof(visit));
        ans=0;num=0;memset(h,0,sizeof(h));
        for (int i=1;i<n;++i){
            int x=read(),y=read(),z=read();
            data[++num].y=y;data[num].z=z;data[num].next=h[x];h[x]=num;
            data[++num].y=x;data[num].z=z;data[num].next=h[y];h[y]=num;
        }sum=n;f[0]=inf;root=0;
        get_root(1,0);solve(root);printf("%d\n",ans);
    }   
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值