HDU 5102 树分治

The K-th Distance

Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 202    Accepted Submission(s): 50


Problem Description
Given a tree, which has n node in total. Define the distance between two node u and v is the number of edge on their unique route. So we can have n(n-1)/2 numbers for all the distance, then sort the numbers in ascending order. The task is to output the sum of the first K numbers.
 

Input
There are several cases, first is the number of cases T. (There are most twenty cases).
For each case, the first line contain two integer n and K ( 2n100000,0Kmin(n(n1)/2,106) ). In following there are n-1 lines. Each line has two integer u , v. indicate that there is an edge between node u and v.
 

Output
For each case output the answer.
 

Sample Input
  
  
2 3 3 1 2 2 3 5 7 1 2 1 3 2 4 2 5
 

Sample Output
  
  
4 10
 

Source


给定一个n个节点的树,求路径长度前k大的所有路径和。

题解方法:把所有边(u,v) 以及(v,u)放入一个队列,队列每弹出一个元素(u,v),对于所有与u相邻的点w,如果w!=v,就把(w,u)入队。这样就能一个一个生成前K小的距离。注意到每条边实际上会入队两次,只要把K翻倍且把ans除2即可,时间复杂度为O(n+K)

由于思路简单,就懒得写代码了,直接抄了yyn代码:

#include <map>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std ;

typedef long long LL ;

#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;
const int MAXE = 200005 ;

struct Node {
    int v , p , d ;
    Node () {}
    Node ( int v , int p , int d ) : v ( v ) , p ( p ) , d ( d ) {}
} ;

struct Edge {
    int v , n ;
    Edge () {}
    Edge ( int v , int n ) : v ( v ) , n ( n ) {}
} ;

Node Q[3000005] ;
Edge E[MAXE] ;
int H[MAXN] , cntE ;
int head , tail ;
LL ans ;
int n , k ;

void clear () {
    ans = 0 ;
    cntE = 0 ;
    clr ( H , -1 ) ;
}

void addedge ( int u , int v ) {
    E[cntE] = Edge ( v , H[u] ) ;
    H[u] = cntE ++ ;
}

void bfs () {
    int cnt = 0 ;
    head = tail = 0 ;
    For ( i , 1 , n ) Q[tail ++] = Node ( i , 0 , 0 ) ;
    while ( head != tail ) {
        Node x = Q[head ++] ;
        int u = x.v , p = x.p ;
        for ( int i = H[u] ; ~i ; i = E[i].n ) {
            int v = E[i].v ;
            if ( v != p ) {
                Q[tail ++] = Node ( v , u , x.d + 1 ) ;
                ans += x.d + 1 ;
                //printf ( "%d->%d %d %d\n" , u , v , cnt , x.d + 1 ) ;
                ++ cnt ;
                if ( cnt == k ) return ;
            }
        }
    }
}

void solve () {
    int u , v ;
    clear () ;
    scanf ( "%d%d" , &n , &k ) ;
    k *= 2 ;
    rep ( i , 1 , n ) {
        scanf ( "%d%d" , &u , &v ) ;
        addedge ( u , v ) ;
        addedge ( v , u ) ;
    }
    bfs () ;
    printf ( "%I64d\n" , ans / 2 ) ;
}

int main () {
    int T ;
    scanf ( "%d" , &T ) ;
    while ( T -- ) solve () ;
    return 0 ;
}

还有一种方法,按照树分治考虑,由于k最大为1000000,那么第k长的路径长度必然数据不是很大,我们可以估计一个上限,然后按照树分治的做法,统计每一个长度的方案数,对于每一个分治重心,枚举每一颗子树,然后统计方案数,最后扫描一遍就行了。

代码:

/* ***********************************************
Author :rabbit
Created Time :2014/11/9 12:03:22
File Name :H.cpp
************************************************ */
#pragma comment(linker, "/STACK:102400000,102400000")
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <string>
#include <time.h>
#include <math.h>
#include <queue>
#include <stack>
#include <set>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-8
#define pi acos(-1.0)
typedef long long ll;
const int maxn=100100;
int head[maxn],tol;
int size[maxn],vis[maxn],fa[maxn],que[maxn];
int num[maxn],ans[maxn];
int maxd,TT;
struct Edge{
	int next,to;
}edge[3*maxn];
inline void addedge(int u,int v){
	edge[tol].to=v;
	edge[tol].next=head[u];
	head[u]=tol++;
}
inline int getroot(int u){
	int Min=maxn,root=0;
	int l,r;
	que[l=r=1]=u;fa[u]=u;
	for(;l<=r;l++)
		for(int i=head[que[l]];i!=-1;i=edge[i].next){
			int v=edge[i].to;
			if(v==fa[que[l]]||vis[v]==TT)continue;
			que[++r]=v;
			fa[v]=que[l];
		}
	for(l--;l;l--){
		int x=que[l],Max=0;
		size[x]=1;
		for(int i=head[x];i!=-1;i=edge[i].next){
			int v=edge[i].to;
			if(v==fa[x]||vis[v]==TT)continue;
			Max=max(Max,size[v]);
			size[x]+=size[v];
		}
		Max=max(Max,r-size[x]);
		if(Max<Min){
			Min=Max;root=x;
		}
	}
	return root;
}
int dis[maxn];
int Q[1001000],S[500100];
inline void go(int root){
	int tot=0;
	for(int i=head[root];i!=-1;i=edge[i].next){
		int u=edge[i].to;
		if(vis[u]==TT)continue;
		fa[u]=root;
		dis[u]=1;
		int l,r,cnt=0;
		que[l=r=1]=u;
		for(;l<=r;l++){
			int x=que[l];
			ans[dis[x]]++;
			Q[cnt++]=dis[x];
			for(int j=1;j<=maxd;j++)
				if(j+dis[x]<=maxd)ans[j+dis[x]]+=num[j];
			for(int j=head[x];j!=-1;j=edge[j].next){
				int v=edge[j].to;
				if(v==fa[x]||vis[v]==TT||dis[x]+1>maxd)continue;
				dis[v]=dis[x]+1;
				fa[v]=x;
				que[++r]=v;
			}
		}
		for(int j=0;j<cnt;j++){
			if(!num[Q[j]])S[tot++]=Q[j];
			num[Q[j]]++;
		}
	}
	for(int i=0;i<tot;i++)num[S[i]]=0;
}
inline void solve(int u){
	int root=getroot(u);
	vis[root]=TT;
	go(root);
	for(int i=head[root];i!=-1;i=edge[i].next){
		int v=edge[i].to;
		if(vis[v]==TT)continue;
		solve(v);
	}
}
int main()
{
     //freopen("data.in","r",stdin);
     //freopen("data.out","w",stdout);
     int T,n,k;
	 cin>>T;
	 memset(vis,0,sizeof(vis));
	 while(T--){
		 scanf("%d%d",&n,&k);
		 maxd=min(k/n+50,k);
		 memset(head,-1,sizeof(head));tol=0;
		 memset(num,0,sizeof(num));
		 memset(ans,0, sizeof(ans));
		 TT++;
		 for(int i=1;i<n;i++){
			 int u,v;
			 scanf("%d%d",&u,&v);
			 addedge(u,v);
			 addedge(v,u);
		 }
		 solve(1);
		 ll sum=0;
		 for(int i=1;i<=maxd;i++){
			 if(ans[i]>=k){
				 sum+=(ll)k*i;
				 break;
			 }
			 k-=ans[i];
			 sum+=(ll)ans[i]*i;
		 }
		 printf("%I64d\n",sum);
	 }
     return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值