您的位置:首页 > 其它

[BZOJ2599][IOI2011]Race-树上启发式合并(dsu on tree)

2017-11-25 21:38 381 查看

Race

Description

给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000

Input

第一行 两个整数 n, k

第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始)

Output

一个整数 表示最小边数量 如果不存在这样的路径 输出-1

Sample Input

4 3

0 1 1

1 2 2

1 3 4

Sample Output

2

想必从搜索引擎的视角上看,这篇博客被“点分治”包围了……

毕竟做这题一开始就是为了练习点分治

思路:

让咱们先放下点分治。

可以发现,每一条路径只会有两种形状:

1.  /    2.   /\
/         /  \
/         /    \


假如咱在每个节点处维护了一个桶,以权值和为下标,记录着从这个点往下走的所有路径中,每种权值和最少需要的边数。(显然是个map而不是真正的桶)

对于第一种,建出f后直接用f[k]更新即可。

考虑第二种,以及如何构建f:

现在假如咱们考虑到了节点u,它的儿子们为v,它的桶为f。

考虑按顺序加入每个儿子,先暴力一点:

对于每个儿子的子树中的点,计算出其到u的相对带权距离dw及深度差d,并用d与f[k−dw]的和来更新答案。

然后用刚才遍历到的每个dw对应的d来更新f[dw]的值。

这样就相当于,对于每个儿子内的点到u的链,咱们用与之带权距离和恰为k的,之前遍历过的另一条链与它一起,拼成一条路径并更新答案。然后,再把它们自己也加入桶中等待后面的子树被匹配。

可以发现这是O(n2)的(用map加个log),而且空间复杂度不敢想象……

考虑优化。

有一种方法叫做启发式合并,其中的树上版叫dsu on tree。

核心思想是,对于每个点,继承其重儿子的桶以保证复杂度。

这样做能保证在暴力遍历轻儿子以更新的情况下,复杂度为O(nlog2n)。

对于这题,也可以这么做:

记两个增量offset[u]和delta[u],分别表示当前节点相对于当前重链的链底的带权距离和距离。这能通过简单的回溯操作计算。

在全局开一个桶(其实是map)f。

每到一个节点,优先递归其轻儿子,最后递归重儿子。

每次结束一个节点的递归时,如果这个点是轻儿子,则清空f,否则不做出操作

那么当回溯回当前节点时,f中存放着重儿子的信息。

先更新好两个增量。

于是在更新和查询时,下标值统一减去offset[u],f内部的值则在查询时加上delta[u],更新时减去delta[u]。

可以发现这样即可实现O(1)继承重儿子。

对于轻儿子,同O(n2)做法相同处理即可。

考虑到每次查询和修改都是O(log2n),于是这么做复杂度为O(nlog22n)

复杂度和点分治貌似相同?

还用了奇慢无比的STL……

然而这个不仅挺快还很短很好写……

毕竟思路这么简单粗暴

目前咱的代码在BZOJ上排名第17~

虽然习惯性的卡了一波常

#include<iostream>
#include<tr1/unordered_map>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;
using namespace std::tr1;

#ifndef _WIN64
#define getchar getchar_unlocked
#endif

inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}

typedef long long ll;
const int N=200009;
const int K=1000001;
const int Inf=2e9+7;

int n,k;
int to[N<<1],nxt[N<<1],w[N<<1],beg
,tot;
int fa
,siz
,fae
,son
,dis
,seg
,dfn;
int id
,ed
,ans,offset
,delta
;
ll dep
;
unordered_map<ll,int> f;

inline void add(int u,int v,int c)
{
to[++tot]=v;
nxt[tot]=beg[u];
w[tot]=c;
beg[u]=tot;
}

inline void chkmin(int &a,int b){if(a>b)a=b;}

inline void dfs_pre(int u)
{
seg[id[u]=++dfn]=u;
siz[u]=1;son[u]=0;
for(register int i=beg[u],v;i;i=nxt[i])
if((v=to[i])!=fa[u])
{
fa[v]=u;
dis[v]=dis[u]+1;
dep[v]=dep[u]+(fae[v]=w[i]);
dfs_pre(v);
siz[u]+=siz[v];
if(!son[u] || siz[v]>siz[son[u]])
son[u]=v;
}
ed[u]=dfn;
}

inline void dfs(int u)
{
if(!son[u])
{
offset[u]=0;
delta[u]=0;
return;
}

for(register int i=beg[u],v;i;i=nxt[i])
if((v=to[i])!=fa[u] && v!=son[u])
dfs(v);
dfs(son[u]);

int &dlt=delta[u],&ofs=offset[u];
ofs=offset[son[u]]+fae[son[u]];
dlt=delta[son[u]]+1;

if(f.count(f[fae[son[u]]-offset[u]]))
chkmin(f[fae[son[u]]-offset[u]],1-dlt);
else
f[fae[son[u]]-offset[u]]=1-dlt;

register ll tmp;
for(register int i=beg[u],v;i;i=nxt[i])
if((v=to[i])!=fa[u] && v!=son[u])
{
for(register int j=id[v];j<=ed[v];j++)
if((tmp=(k-(dep[seg[j]]-dep[u])))>0 && f.count(tmp-ofs))
chkmin(ans,f[tmp-ofs]+dis[seg[j]]-dis[u]+dlt);
for(register int j=id[v];j<=ed[v];j++)
if((tmp=dep[seg[j]]-dep[u])<=k)
{
if(f.count(tmp-ofs))
chkmin(f[tmp-ofs],dis[seg[j]]-dis[u]-dlt);
else
f[tmp-ofs]=dis[seg[j]]-dis[u]-dlt;
}
}

if(f.count(k-ofs))
chkmin(ans,f[k-ofs]+dlt);
if(son[fa[u]]!=u && u!=1)
f.clear();
}

int main()
{
n=read();k=read();
for(int i=1,u,v,w;i<n;i++)
{
u=read()+1;v=read()+1;w=read();
add(u,v,w);add(v,u,w);
}

dfs_pre(1);
ans=1e9+7;
dfs(1);

printf("%d\n",ans==(int)(1e9+7)?-1:ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: