树链剖分-点的分治(dis[i]+dis[j]<=k的点对数量)

本文介绍了一种解决特定类型图论问题的有效方法——树的点分治算法,并通过一个具体的POJ题目进行了详细的解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

poj1741

Tree
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 10012 Accepted: 3024

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001). 
Define dist(u,v)=The min distance between node u and v. 
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. 
Write a program that will count how many pairs which are valid for a given tree. 

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l. 
The last test case is followed by two zeros. 

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8
题意:

给出一个n个顶点的树形图,找出<u,v>路径的距离不超过k的值,问存在多少对这样的点对(认为<u,v>和<v,u>是一种情况,没有<u,u,>的情况)

分析:

因为n<=10000,暴搜一定会超时,所以很明显用的是树的点分治进行处理:点分治即为把树进行递归,分别对每个子树进行操作,然后把每个子树的情况综合起来,对于这道题目,首先找到树根(即树的重心),对于该树,统计dis[i]+dis[j]<=k的数量,

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。

如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。

在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。

树的重心的算法可以线性求解。

程序:

#include"string.h"
#include"stdio.h"
#include"stdlib.h"
#include"queue"
#include"stack"
#include"iostream"
#include"algorithm"
#include"vector"
#define inf 999999999
#define M 11111
using namespace std;
struct node
{
    int u,v,w,next;
}edge[M*3];
int t,head[M],use[M],dis[M],son[M],limit[M],k,cnt,MN,ID;
void init()
{
    t=0;
    memset(head,-1,sizeof(head));
}
void add(int u,int v,int w)
{
    edge[t].u=u;
    edge[t].v=v;
    edge[t].w=w;
    edge[t].next=head[u];
    head[u]=t++;
}
void dfs_size(int u,int f)
{
    son[u]=1;
    limit[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(f!=v&&!use[v])
        {
            dfs_size(v,u);
            son[u]+=son[v];
            limit[u]=max(limit[u],son[v]);
        }
    }
}
void dfs_root(int root,int u,int f)
{
    if(son[root]-son[u]>limit[u])
        limit[u]=son[root]-son[u];
    if(MN>limit[u])
    {
        MN=limit[u];
        ID=u;
    }
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(f!=v&&!use[v])
        {
            dfs_root(root,v,u);
        }
    }
}//以上两个深搜是找树的重心,记录到ID中
void dfs_dis(int u,int f,int id)
{
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(f!=v&&!use[v])
        {
            dfs_dis(v,u,id+edge[i].w);
        }
    }
    dis[cnt++]=id;
}//记录子树当前根到其他点的距离
int cal(int u,int f,int id)
{
    cnt=0;
    int sum=0,i,j;
    dfs_dis(u,f,id);
    sort(dis,dis+cnt);
    for(i=0,j=cnt-1;i<cnt;i++)
    {
        while(i<j&&dis[i]+dis[j]>k)
            j--;
        if(j<=i)
            break;
        sum+=j-i;
    }
    return sum;
}
int dfs_ans(int root,int u,int f)
{
    int sum;
    dfs_size(u,f);
    MN=inf;
    dfs_root(root,u,f);
    sum=cal(ID,ID,0);//先搜根节点到孩子的距离,且求出有多少点对距离小于等于K
    use[ID]=1;
    for(int i=head[ID];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(!use[v])
        {
            sum-=cal(v,v,edge[i].w);//从根节点开始搜的话,会出现子树会搜到的重复的情况,应该减去
            sum+=dfs_ans(v,v,v);
        }
    }
    return sum;
}
int main()
{
    int n,i;
    while(scanf("%d%d",&n,&k),k||n)
    {
        init();
        for(i=1;i<n;i++)
        {
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        memset(use,0,sizeof(use));
        int ans=dfs_ans(1,1,1);
        printf("%d\n",ans);
    }
}





这段代码实现了计算最小弦长三角剖分的算法,具体解释如下: 1. 引入必要的头文件和命名空间: ```c++ #include <iostream> #include <cstdio> #include <cmath> using namespace std; ``` 2. 定义存储顶间距离、最优值以及最优解的数组: ```c++ int w[100][100],t[100][100],s[100][100]; ``` 其中 `w` 数组存储了每个顶之间的距离,`t` 数组存储了计算出来的最优值,`s` 数组存储了对应的最优解。 3. 定义权函数 `weight(i,j,k)`,此处为最小弦长三角剖分: ```c++ int weight(int i,int j,int k) { return w[i][j]+w[i][k]+w[j][k]; } ``` 4. 定义结构体 `node` 用于存储顶的坐标: ```c++ struct node { int x,y; }p[100]; ``` 5. 定义计算最优值的函数 `Min_Weight_Triangulation(int n,int t[][100],int s[][100])`: ```c++ void Min_Weight_Triangulation(int n,int t[][100],int s[][100]) { for(int i=1;i<=n;i++) t[i][i]=0; for(int r=2;r<=n;r++) { for(int i=1;i<=n-r+1;i++) { int j=i+r-1; t[i][j]=t[i+1][j]+weight(i-1,i,j); s[i][j]=i; for(int k=i+1;k<j;k++) { int q=t[i][k]+t[k+1][j]+weight(i-1,k,j); if(q<t[i][j]) { t[i][j]=q; s[i][j]=k; } } } } } ``` 函数中使用了动态规划的思想,对于每个子问题都计算出其最优值和最优解。具体地,对于长度为 `r` 的子段,枚举其起 `i`,终 `j`,以及断 `k`,计算出对应的最优值 `q`。最后得到整个问题的最优值和最优解。 6. 定义构造最优三角剖分的函数 `Traceback(int i,int j,int s[][100])`: ```c++ void Traceback(int i,int j,int s[][100]) { if(i==j) return; Traceback(i,s[i][j],s); Traceback(s[i][j]+1,j,s); cout<<i-1<<s[i][j]<<j<<endl; } ``` 函数中使用了递归的思想,从最优解的根节开始递归构造最优解。 7. 在 `main` 函数中读入顶坐标,计算顶间距离,调用计算最优值和最优解的函数,最后输出最优解: ```c++ int main() { int n; cin>>n; for(int i=0;i<n;i++) cin >> p[i].x >> p[i].y; for(int i=0;i<n;i++) w[i][i]=0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) { int disx=p[i].x-p[j].x; int disy=p[i].y-p[j].y; w[i][j]=sqrt(disx*disx+disy*disy); } Min_Weight_Triangulation(n,t,s); Traceback(1,n-1,s); return 0; } ``` 以上就是这段代码的全部内容和解释。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值