bzoj3784 树上的路径

传送门(权限题)​​​​​​

大致题意:给定一个N个结点的树,结点用正整数1..N编号。每条边有一个正整数权值。用d(a,b)表示从结点a到结点b路边上经过边的权值。其中要求a<b.将这n*(n-1)/2个距离从大到小排序,输出前M个距离值。

 

此题可看成树上的“超级钢琴”。

如果当前分治中心的dfs序为x,那么最左边子树的dfs序区间为[x+1,r1],第二个为[r1+1,r2]。。。以此类推。

每个子树都和它前面的点匹配:比如[r1+1,r2]的这些点就和[x,r1]的这些点匹配。(含分治中心)然后[r2+1,r3]的这些点和[x,r2]的匹配。也就是说在同一个分治中心,左边界是不变的,随着dfs的进行,右边界在变化。

 

我们记录每个点到分治中心的距离,现在将问题重新理一下:

两个点确定一条路径(该路径一定经过分治中心),而对于一个固定的点,它的另一个端点有一个范围(通过点分治序来体现)。我们现在要求前若干大的路径。这就和超级钢琴是一样的问题了。

看一下具体实现:

dis数组下标为点分治序,记录对应点到分治中心的距离。搜完一个子树后,R要往后移动。

premax记录前面子树出现的最长距离,tmp记录当前子树的最长距离。跑完一个子树后更新一下premax。

inline void solve(int u){
	vis[u]=1,dis[L=++tot]=0,premax=0;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]) tmp=0,R=tot,getdis(V[i],u,W[i],premax),premax=max(tmp,premax);
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]) root=0,SZ=sz[V[i]],getroot(V[i],u),solve(root);
}

 

我用q数组记录“超级和弦”。也就是右端点固定,左端点可动的不定路径。我们把这些超级和弦丢进优先队列里。

这里说一下:node是一个四元组,l,r,sum,id分别记录左端点左边界的点分治序、左端点右边界的点分治序、范围内的最长路长度、当前点的点分治序。

void getdis(int u,int f,int sum,int pre){
	dis[++tot]=sum,tmp=max(tmp,sum),Q.push(node(L,R,sum+pre,tot));
	for(int i=Head[u];i;i=Next[i])if(GG)getdis(V[i],u,sum+W[i],pre);
}

 

我在这里犯了个错误:

void getdis(int u,int f,int len,int edge){
	dis[++tot]=len+edge,q[++num]=node(L,R,0,tot);
	for(int i=Head[u];i;i=Next[i])if(GG)getdis(V[i],u,dis[tot],W[i]);
}

之前写getdis写成了这样。。。这里tot是会变的。

搜过一个子树后,dis[tot]就变成了前一个子树的最后一个节点到当前分治中心的距离。。。。然后距离就炸了,,半天没看出来。。。233。。把dis[tot]改成len+edge就对了。

 

#include<bits/stdc++.h>
#define GG ((V[i]!=f)&&(!vis[V[i]]))
#define K int(log2(r-l+1))
using namespace std;
const int maxn=5e4+10;
const int maxm=maxn*20;
int root=0,tot=0,SZ,vis[maxn],sz[maxn],mx[maxn],dis[maxm],st[maxm][20],premax=0,tmp=0;
int n,m,u,v,w,L,R,Head[maxn],V[maxn<<1],Next[maxn<<1],W[maxn<<1],cnt=0,num=0,d1,d2,pos;
struct node{int l,r,sum,id;}p;priority_queue<node> Q;
inline bool operator<(const node &a,const node &b){return a.sum<b.sum;}
inline void add(int u,int v,int w){++cnt,Next[cnt]=Head[u],W[cnt]=w,V[cnt]=v,Head[u]=cnt;}
inline int MAX(int a,int b){return (dis[a]>dis[b])?(a):(b);}
inline int query(int l,int r){return (l>r)?(-1):(MAX(st[l][K],st[r-(1<<K)+1][K]));}
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
void getroot(int u,int f){
	sz[u]=1,mx[u]=0;
	for(int i=Head[u];i;i=Next[i])if(GG)
	getroot(V[i],u),sz[u]+=sz[V[i]],mx[u]=max(mx[u],sz[V[i]]);
	mx[u]=max(mx[u],SZ-sz[u]); if(mx[root]>mx[u]) root=u;
}
void getdis(int u,int f,int sum,int pre){
	dis[++tot]=sum,tmp=max(tmp,sum),Q.push((node){L,R,sum+pre,tot});
	for(int i=Head[u];i;i=Next[i])if(GG)getdis(V[i],u,sum+W[i],pre);
}
inline void solve(int u){
	vis[u]=1,dis[L=++tot]=0,premax=0;
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]) tmp=0,R=tot,getdis(V[i],u,W[i],premax),premax=max(tmp,premax);
	for(int i=Head[u];i;i=Next[i]) if(!vis[V[i]]) root=0,SZ=sz[V[i]],getroot(V[i],u),solve(root);
}
inline void pre_st(){
	for(int i=0;(1<<i)<=tot;++i) for(int j=1;(((1<<i)+j-1)<=tot);++j)
	if(i) st[j][i]=MAX(st[j][i-1],st[j+(1<<(i-1))][i-1]); else st[j][i]=j;
}
int main(){
	SZ=mx[0]=n=read(),m=read();
	for(int i=1;i<n;++i) u=read(),v=read(),w=read(),add(u,v,w),add(v,u,w);
	getroot(1,0),solve(root),pre_st();
	for(int i=1;i<=m;++i){
		p=Q.top(),Q.pop(),printf("%d\n",p.sum);
		pos=query(p.l,p.r),d1=query(p.l,pos-1),d2=query(pos+1,p.r);
		if(d1!=-1) Q.push((node){p.l,pos-1,dis[p.id]+dis[d1],p.id});
		if(d2!=-1) Q.push((node){pos+1,p.r,dis[p.id]+dis[d2],p.id});
	}
}

压行真的很爽。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值