树分治(点)

 基本思想:把树分割成几个子树,从而快速解决一些问题(如树上距离小于等于k的点对数)。关键是如何取分割树,不难想到,如果能够找到一个点,这个点下面的各个子树的大小都一样,肯定是个最优点,也叫重心。当然都一样是理想情况,在实际中只要保证重心下面的子树中最大的尺寸是最小的即可。

模板题:poj 1741 树中距离小于等于k的对数

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn=2e5+5;

bool vis[maxn];//vis:是否已经是被割的点
int dis[maxn],dep[maxn],head[maxn],maxsz[maxn],sz[maxn];
int cnt,tot,G,ans,k,n;
//dep:到重心距离,maxsz:最大子树尺寸,sz:子树尺寸
struct node
{
    int v,w,nxt;
}edge[maxn];

void addedge(int u,int v,int w)
{
    edge[++cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].nxt=head[u];
    head[u]=cnt;
}

void getG(int x,int fa)//找重心
{
    maxsz[x]=0;
    sz[x]=1;
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(v==fa||vis[v])continue;
        getG(v,x);
        sz[x]+=sz[v];
        maxsz[x]=max(maxsz[x],sz[x]);
    }
    maxsz[x]=max(maxsz[x],tot-sz[x]);//sz[x]很小时,另一边可能会比较大
    G=maxsz[x]<maxsz[G]?x:G;
}

void getDis(int x,int fa)
{
    dis[++dis[0]]=dep[x];//dis[0],用来计数,不用额外声明一个变量了
    for(int i=head[x];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        int w=edge[i].w;
        if(v==fa||vis[v])continue;
        dep[v]=dep[x]+w;
        getDis(v,x);
    }
}

int cal(int g,int pre)
{
    dis[0]=0;
    dep[g]=pre;
    getDis(g,-1);
    sort(dis+1,dis+1+dis[0]);
    int ret=0;
    for(int l=1,r=dis[0];l<r;)//类似二分
    {
        if(dis[l]+dis[r]<=k)ret+=r-l,l++;
        else r--;
    }
    return ret;
}

void divide(int g)
{
    ans+=cal(g,0);
    vis[g]=true;
    for(int i=head[g];~i;i=edge[i].nxt)
    {
        int v=edge[i].v;
        int w=edge[i].w;
        if(vis[v])continue;
        ans-=cal(v,w);//可能会有两个点在同一棵子树中,这些要排除
        tot=maxsz[0]=sz[v];
        G=0;
        getG(v,-1);
        divide(G);
    }
}

void init()//初始化
{
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    tot=maxsz[0]=n;
    ans=dis[0]=cnt=G=0;
}

int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        if(n==0&&k==0)break;
        init();
        int x,y,w;
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&x,&y,&w);
            addedge(x,y,w);
            addedge(y,x,w);
        }
        getG(1,-1);
        divide(G);
        printf("%d\n",ans);
    }
    return 0;
}

 

gym101991 A

题意:给出一棵树,添加一条边,使得图中的桥的数量范围为[l,r]

思路:一条长度为n的链中有n个桥,环中是没有桥的。给长度为n的链的两端加边,则减少了n个桥。可以算出可以减少桥的数量范围为[n-1-r,n-1-l],记R=n-1-l,L=n-1-r。因为长度都是整数,所以可以算出长度小于等于R的点对数和长度小于等于L-1的点对数,最后答案就是前者减去后者。直接套用模板即可。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值