POJ-1741-Tree (点分治)

POJ-1741-Tree

点分治第一题。

点分治主要用于统计满足某一性质的链或其数量。其主要思想是找重心->删重心->迭代子树。

每次迭代可以将子树分成近似相等两部分,这样就能处理两种类型的链:横跨重心和在子树内。

这样就可以在 O(nlogn) 的时间内找出所有的链,并进行处理。

这题统计和,所以要排序,因此这题的复杂度是 O(nlog2n)

#include<algorithm>
#include<vector>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N=1e4+7;
bool del[N];
int d[N],f[N],sz[N],ans,size,root,n,k;
struct Edge
{
    int v,c;
    Edge(int v,int c) : v(v),c(c) {}
    Edge();
};
vector<Edge> adj[N];
vector<int> dep;
inline void addedge(int u,int v,int c)
{
    adj[u].push_back(Edge(v,c));
    adj[v].push_back(Edge(u,c));
}
// f[u]的含义为以u为root的情况下,所有子树中大小最大的那棵sz
// 重心就是 f 最小的那个点
void getroot(int u,int p)
{
    sz[u]=1;f[u]=0;
    for(int i=0;i<adj[u].size();++i)
    {
        int v=adj[u][i].v;
        if(v==p||del[v]) continue;
        getroot(v,u);
        sz[u]+=sz[v];
        f[u]=max(f[u],sz[v]);
    }
    f[u]=max(f[u],size-sz[u]);
    if(f[u]<f[root]) root=u;
}
void getdeep(int u,int p)
{
    dep.push_back(d[u]);
    sz[u]=1;
    for(int i=0;i<adj[u].size();++i)
    {
        int v=adj[u][i].v,c=adj[u][i].c;
        if(del[v]||v==p) continue;
        d[v]=d[u]+c;
        getdeep(v,u);
        sz[u]+=sz[v];
    }
}
int cal(int u,int init)
{
    dep.clear();d[u]=init;
    getdeep(u,0);
    sort(dep.begin(),dep.end());
    int res=0;
    int l=0,r=dep.size()-1;
    while(l<=r)
    {
        if(dep[l]+dep[r]<=k) res+=r-l,++l;
        else --r;
    }
    return res;
}
// 横跨u的两条链都能分成两段,因此对这两段进行双指针处理
// 后面减去在同一个子树中的两段形成的部分。
void solve(int u)
{
    ans+=cal(u,0);
    del[u]=true;
    for(int i=0;i<adj[u].size();++i)
    {
        int v=adj[u][i].v;
        if(del[v]) continue;
        ans-=cal(v,adj[u][i].c);
        f[0]=size=sz[v];
        root=0;
        getroot(v,0);
        solve(root);
    }
}
int main()
{
    while(~scanf("%d%d",&n,&k))
    {
        if(n==0&&k==0) break;
        for(int i=1;i<=n;++i) adj[i].clear();
        for(int i=1;i<n;++i)
        {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            addedge(u,v,c);
        }
        ans=0;
        memset(del,0,sizeof(del));
        f[root=0]=size=n;
        getroot(1,0);
        solve(root);
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值