点分治——POJ 1741

写的第一道点分治的题目,权当认识点分治了。

点分治,就是对每条过某个点的路径进行考虑,若路径不经过此点,则可以对其子树进行考虑。

具体可以看menci的blog:点分治

来看一道例题:POJ 1741 Tree

题目大意:扔给你一颗有权无根树,求有多少条路径的长度小于k;

解题思路:先找出重心,用一次dfs处理出每个点到根的距离dis,然后将dis[]排序,用O(n)的复杂度处理出"过根且长度小于等于k的路径数目",删除根节点,对于每棵子树重复上述操作。

注意要去重:

像上面这样一个图,假设每条边的长度为1,即点到根的路径长等于点的深度,设k=4。此时dis[6]=2,dis[7]=2;dis[6]+dis[7]=4=k。但是当分治以③为根节点的子树时,dis[6]+dis[7]=2<k,这样6->7这条路径就被计算了两次,所以每次求完过根节点的符合条件的路径数之后,要减去在同一棵子树下的节点之间的符合条件的路径数。

代码:

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring>
 4 using namespace std;
 5
 6 struct edge{
 7     int to,next,w;
 8 }e[20030];
 9
10 int root,ans,u,v,l,n,kk,ne,size;
11 int head[10009],s[10009],d[10009],dis[10009],f[10009];
12 //s[k]为k的子节点数目,dis[k]为k到根节点的距离
13 bool b[10009];
14
15 void add(int a,int b,int c){
16     e[++ne].to=b; e[ne].w=c; e[ne].next=head[a]; head[a]=ne;
17 }
18
19 void getroot(int k,int fa){
20     int v,i;
21     s[k]=1;
22     f[k]=0;
23     for(i=head[k];i!=-1;i=e[i].next){
24         v=e[i].to;
25         if(v!=fa&&!b[v]){
26             getroot(v,k);
27             s[k]+=s[v];//递归求子节点数目
28             f[k]=max(f[k],s[v]);
29         }
30     }
31     f[k]=max(f[k],size-s[k]);//dp求重心
32     if(f[k]<f[root])root=k;
33 }
34
35 void getdis(int k,int fa){
36     int i,v;
37     d[++d[0]]=dis[k];//储存距离以便排序
38     for(i=head[k];i!=-1;i=e[i].next){
39         v=e[i].to;
40         if(v!=fa&&!b[v]){
41             dis[v]=dis[k]+e[i].w;//dfs求距离
42             getdis(v,k);
43         }
44     }
45 }
46
47 int clac(int k,int init){
48     int i,j,ret=0;
49     d[0]=0;
50     dis[k]=init;
51     getdis(k,0);
52     sort(d+1,d+1+d[0]);
53     for(i=1,j=d[0];i<j;)
54         if(d[i]+d[j]<=kk)ret+=j-i++;//计算路径数
55         else j--;
56     return ret;
57 }
58
59
60 void work(int k){
61     int v,i;
62     ans+=clac(k,0);//计算路径数并加在ans上
63     b[k]=true;//标记重心为删除
64     for(i=head[k];i!=-1;i=e[i].next){
65         v=e[i].to;
66         if(!b[v]){
67             ans-=clac(v,e[i].w);//去重
68             f[0]=size=s[v];
69             getroot(v,root=0);//更新重心
70             work(root);//对子树求解
71         }
72     }
73 }
74
75 int main(){
76     int i;
77     while(scanf("%d%d",&n,&kk)==2){
78         if(n==0&&kk==0)break;
79         for(i=1;i<=n;i++){
80             head[i]=-1;
81             b[i]=0;
82         }
83         ne=0;
84         for(i=1;i<=n-1;i++){
85             scanf("%d%d%d",&u,&v,&l);
86             add(u,v,l);
87             add(v,u,l);//注意要连双向边
88         }
89         root=0;
90         f[0]=size=n;
91         getroot(1,0);//求重心
92         ans=0;
93         work(root);
94         printf("%d\n",ans);
95     }
96     return 0;
97 }

总体上感觉点分治不是很难理解,只要对题目有思路应该能写出来。(orz就是写代码经常出错!b[v]写成!b[k]之类的还查不出来)

时间: 2024-10-28 19:51:42

点分治——POJ 1741的相关文章

树上点分治 poj 1741

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. Wri

【POJ 1741】 Tree (树的点分治)

Tree 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)

【点分治】【POJ 1741】【cogs 1714】树上的点对

1714. [POJ1741][男人八题]树上的点对 ★★★ 输入文件:poj1741_tree.in 输出文件:poj1741_tree.out 简单对比 时间限制:1 s 内存限制:256 MB [题目描述] 给一棵有n个节点的树,每条边都有一个长度(小于1001的正整数). 定义dist(u,v)=节点u到节点v的最短路距离. 给出一个整数k,我们称顶点对(u,v)是合法的当且仅当dist(u,v)不大于k. 写一个程序,对于给定的树,计算有多少对顶点对是合法的. [输入格式] 输入包含多

POJ 1741 树分治(点分治模板题)

POJ 1741 题意:求一棵树中点对<=k的数量. 总结:点分治,搞不太懂..大概敲了一遍 #include<iostream> #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #include<string> #include<cmath> #include<queue> #include<stac

poj 1741 树的点分治(入门)

Tree Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 18205   Accepted: 5951 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

树的点分治 (poj 1741, 1655(树形dp))

poj 1655:http://poj.org/problem?id=1655 题意: 给无根树,  找出以一节点为根,  使节点最多的树,节点最少. 题解:一道树形dp,先dfs 标记 所有节点的子树的节点数. 再dfs  找出以某节点为根的最大子树,节点最少. 复杂度(n) /***Good Luck***/ #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstdio> #include <cs

POJ 1741 Tree (树上点分治)(楼教主男人八题之一)

题目地址:POJ 1741 树分治第一发! 树分治详情请看漆子超的国家集训队论文,论文传送门 树分治裸题. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm> #include <stdlib.h> #include <map> #include <set> #

poj 1741 楼教主男人八题之一:树分治

http://poj.org/problem?id=1741 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

poj 1741 Tree(树的点分治)

poj 1741 Tree(树的点分治) 给出一个n个结点的树和一个整数k,问有多少个距离不超过k的点对. 首先对于一个树中的点对,要么经过根结点,要么不经过.所以我们可以把经过根节点的符合点对统计出来.接着对于每一个子树再次运算.如果不用点分治的技巧,时间复杂度可能退化成\(O(n^2)\)(链).如果对于子树重新选根,找到树的重心,就一定可以保证时间复杂度在\(O(nlogn)\)内. 具体技巧是:首先选出树的重心,将重心视为根.接着计算出每个结点的深度,以此统计答案.由于子树中可能出现重复