卧薪尝胆,厚积薄发。
树上的路径
Date: Thu Feb 14 21:14:55 CST 2019
In Category:
NoCategory
Description:
给出一棵树,边有边权,求树上的前
$k$
小路径。
$1\leqslant n\leqslant 10^6$
Solution:
思考一下如果我们有了一个堆,这个堆里有树上的所有路径,那么我们只要
$pop\ k$
次就行了。
其实是可以做到的,设
$f[i]$
表示
$i$
这个点为最高点的直上直下的一条链的堆,这个显然可以用可并堆合并儿子节点打标记来计算,
$g[i]$
表示从
$i$
的父亲到达
$i$
的路径的堆,这个可以用可并堆合并
$g[fa[i]]$
还有子树的前缀
$f$
堆和后缀
$f$
堆再打标记来计算,注意必须可持久化,最后把每个点的
$f$
和
$g$
都合并在一起就得到了一个包含所有路径的堆。
但是这题空间不够所以不能这样做。
有一个东西叫做点分治序,具体来说就是把点分治时经过的序列记录下来,长度为
$n\log n$
,这个序列的一个重要的性质是对于当前这个分治重心,假设我们已经知道了从他出发的一条链,我们想找一个从他出发的另一条链,那么会发现这条链的端点在点分序上是一段连续的区间,然后问题就变成了每个
$a$
可以选的范围都是连续的一段
$b$
,一个
$pair(a,b)$
的权值是
$a$
和
$b$
的权值之和,求第
$K$
大,类似超级钢琴,用一个
$ST$
表然后一个堆就行了。
Code:
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cctype>
#include<cstring>
using namespace std;
int n,m;
#define MAXN 50010
struct edge
{
int to,nxt,v;
}e[MAXN << 1];
int edgenum = 0;
int lin[MAXN] = {0};
void add(int a,int b,int v)
{
e[++edgenum] = (edge){b,lin[a],v};lin[a] = edgenum;
e[++edgenum] = (edge){a,lin[b],v};lin[b] = edgenum;
return;
}
#define MAXL (MAXN * 20)
int dfn[MAXL],mid[MAXL],len[MAXL],lef[MAXL],rig[MAXL],tot = 0;//µã·ÖÐò
int st[MAXL][26];
#define INF 0x3f3f3f3f
int root,s,siz[MAXN],d[MAXN];
bool vis[MAXN];
void getroot(int k,int fa)
{
siz[k] = 1;d[k] = 0;
for(int i = lin[k];i != 0;i = e[i].nxt)
{
if(vis[e[i].to] || e[i].to == fa)continue;
getroot(e[i].to,k);
siz[k] += siz[e[i].to];
d[k] = max(d[k],siz[e[i].to]);
}
d[k] = max(d[k],s - siz[k]);
if(d[k] < d[root])root = k;
return;
}
void getd(int k,int fa,int d)
{
dfn[++tot] = k;mid[tot] = mid[tot - 1];len[tot] = d;
lef[tot] = lef[tot - 1];
rig[tot] = (rig[tot] ? rig[tot] : rig[tot - 1]);
for(int i = lin[k];i != 0;i = e[i].nxt)
{
if(vis[e[i].to] || e[i].to == fa)continue;
getd(e[i].to,k,d + e[i].v);
}
return;
}
void divide(int rt)
{
vis[rt] = true;
dfn[++tot] = rt;mid[tot] = rt;
lef[tot] = tot;rig[tot] = tot - 1;
for(int i = lin[rt];i != 0;i = e[i].nxt)
{
if(vis[e[i].to])continue;
rig[tot + 1] = tot;
getd(e[i].to,0,e[i].v);
}
for(int i = lin[rt];i != 0;i = e[i].nxt)
{
if(vis[e[i].to])continue;
root = 0;s = siz[e[i].to];
getroot(e[i].to,0);
divide(root);
}
}
int mymax(int a,int b)
{
return (len[a] > len[b] ? a : b);
}
int lg[MAXL];
int query(int l,int r)
{
int len = lg[r - l + 1];
return mymax(st[l][len],st[r - (1 << len) + 1][len]);
}
struct node
{
int s,l,r,p,v;
friend bool operator < (const node& a,const node& b)
{
return a.v < b.v;
}
};
priority_queue<node> q;
int main()
{
scanf("%d%d",&n,&m);
int a,b,c;
for(int i = 1;i < n;++i)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
s = n;root = 0;d[0] = INF;
getroot(1,0);
divide(root);
for(int i = 1;i <= tot;++i)st[i][0] = i;
for(int i = 2;i <= tot;++i)lg[i] = lg[i >> 1] + 1;
for(int k = 1,l = 1;k <= 25;++k,l = l << 1)
for(int i = 1;i <= tot - l * 2 + 1;++i)
st[i][k] = mymax(st[i][k - 1],st[i + l][k - 1]);
int sum = 0;
for(int i = 1;i <= tot;++i)sum += rig[i] - lef[i] + 1;
for(int i = 1;i <= tot;++i)
{
if(lef[i] > rig[i])continue;
q.push((node){i,lef[i],rig[i],query(lef[i],rig[i]),len[query(lef[i],rig[i])] + len[i]});
}
for(int i = 1;i <= m;++i)
{
node k = q.top();q.pop();
printf("%d\n",k.v);
if(k.l < k.p)
{
q.push((node){k.s,k.l,k.p - 1,query(k.l,k.p - 1),len[query(k.l,k.p - 1)] + len[k.s]});
}
if(k.p < k.r)
{
q.push((node){k.s,k.p + 1,k.r,query(k.p + 1,k.r),len[query(k.p + 1,k.r)] + len[k.s]});
}
}
return 0;
}
In tag:
数据结构-可持久化左偏树 树-点分治序
Copyright © 2020
wjh15101051
ღゝ◡╹)ノ♡