poj 1741(1987) 树分治(求距离小于某值的点对数)

时间:2022-02-17 19:25:21

题意:给定一棵带权无向树,并给定一个整数m。求树上点间最短路不大于m的点对数量。(1987题意完全相同,又重新写一遍熟练一下)

思路:比较明显的思路是用LCA来做,但问题是用LCA的话,当前问题的查询数量级为n^2,所以不管使用离线LCA还是在线LCA都逃不过O(n^2)的复杂度。经过学习知道了树分治这种东西。

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。
在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。
树的重心的算法可以线性求解,关于树的重心的定义和解法可以参见poj1655。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define clr(s,t) memset(s,t,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 10005
struct edge{
int y,w,next;
}e[N<<1];
int n,m,top,res,root,len,size;
int first[N],used[N],son[N],rnum[N],dis[N],q[N];
void init(){
top = res = root = 0;
clr(first, -1);
clr(used, 0);
clr(son, 0);
clr(rnum, 0);
rnum[0] = N;
}
int cmp(const void* a,const void* b){
return (*(int*)a) - (*(int*)b);
}
void add(int x,int y,int w){
e[top].y = y;
e[top].w = w;
e[top].next = first[x];
first[x] = top++;
}
void findroot(int x,int fa){
int i,y;
son[x] = 1;
rnum[x] = 0;
for(i = first[x];i!=-1;i=e[i].next){
y = e[i].y;
if(y!=fa && !used[y]){
findroot(y, x);
son[x] += son[y];
rnum[x] = max(rnum[x], son[y]);
}
}
rnum[x] = max(rnum[x], size-son[x]);
if (rnum[x] < rnum[root])
root = x;
}
void getdis(int x,int fa){
int i;
q[len++] = dis[x];
son[x] = 1;
for(i = first[x];i!=-1;i=e[i].next)
if(e[i].y != fa &&!used[e[i].y]){
dis[e[i].y] = dis[x]+e[i].w;
getdis(e[i].y, x);
son[x] += son[e[i].y];
}
}
int update(int len){
int i,j,sum=0;
qsort(q, len, sizeof(int), cmp);
for(i = 0,j = len-1;;i++){
while(q[i]+q[j]>m)
j--;
if(i>=j)
break;
sum += j-i;
}
return sum;
}
void tdc(int x){//tree_divide_conquer
int i,y;
findroot(x,root=0);
len = 0;
dis[root] = 0;
getdis(root,0);
res += update(len);
used[root] = 1;
for(i = first[root];i!=-1;i=e[i].next)
if(!used[y=e[i].y]){
len = 0;
dis[y] = e[i].w;
getdis(y,root);
res -= update(len);
size = son[y];
tdc(y);
}
}
int main(){
while(scanf("%d %d",&n,&m) && (n+m)){
int i,a,b,w;
init();
for(i = 1;i<n;i++){
scanf("%d %d %d",&a,&b,&w);
add(a,b,w);add(b,a,w);
}
size = n;
tdc(1);
printf("%d\n",res);
}
return 0;
}

1987:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define clr(s,t) memset(s,t,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 40005
struct edge{
int y,w,next;
}e[N<<1];
int first[N],used[N],son[N],tnum[N],q[N],dis[N];
int top,n,m,th,res,len,size,root;
void init(){
top = res = len = 0;
clr(first, -1);
clr(used, 0);
tnum[0] = N;
}
void add(int x,int y,int w){
e[top].y = y;
e[top].w = w;
e[top].next = first[x];
first[x] = top++;
}
int cmp(const void* a,const void* b){
return (*(int*)a) - (*(int*)b);
}
void getroot(int x,int fa){
int i,y;
son[x] = 1;
tnum[x] = 0;
for(i = first[x];i!=-1;i=e[i].next){
y = e[i].y;
if(!used[y] && y!=fa){
getroot(y,x);
son[x] += son[y];
tnum[x] = max(tnum[x], son[y]);
}
}
tnum[x] = max(tnum[x], size-son[x]);
if(tnum[x] < tnum[root])
root = x;
}
void getdis(int x,int fa){
int i,y;
q[len++] = dis[x];
son[x] = 1;
for(i = first[x];i!=-1;i=e[i].next){
y = e[i].y;
if(!used[y] && y!=fa){
dis[y] = dis[x]+e[i].w;
getdis(y,x);
son[x] += son[y];
}
}
}
int updatedis(){
int i,j,sum = 0;
qsort(q, len, sizeof(int), cmp);
for(i = 0,j = len-1;;i++){
while(q[i]+q[j] > th)
j--;
if(i>=j)
break;
sum += j-i;
}
return sum;
}
void tdc(int x){
int i,y;
getroot(x,root = 0);
dis[root] = len = 0;
getdis(root,0);
res += updatedis();
used[root] = 1;
for(i = first[root];i!=-1;i=e[i].next){
y = e[i].y;
if(!used[y]){
dis[y] = e[i].w;
len = 0;
getdis(y, root);
res -= updatedis();
size = son[y];
tdc(y);
}
}
}
int main(){
int i,a,b,c;
char ch;
init();
scanf("%d %d",&n,&m);
for(i = 0;i<m;i++){
scanf("%d %d %d %c",&a,&b,&c,&ch);
add(a,b,c);
add(b,a,c);
}
scanf("%d",&th);
size = n;
tdc(1);
printf("%d\n",res);
return 0;
}