洛谷P4149 [IOI2011]Race
题目链接
题目大意
给出一棵树,每条边有权。求一条简单路径,使得路径和等于 \(k\),且边的数量最小。
问最小数量是多少(若没有满足条件的则输出 -1)
解题思路
定义 \(dis_u\) 表示节点 \(u\) 到根节点的距离,\(dep_u\) 表示节点 \(u\) 的深度
那么树上任意两点 \(u , v\) 的简单路径和等于 \(dis_u + dis_v - 2 × dis_{lca(u , v)}\)
两点之间边的数量为 \(dep_u + dep_v - 2 × dep_{lca(u , v)}\)
那么问题就转换成在树上找到两点 $ u , v $ 使得 \(dis_u + dis_v - 2 × dis_{lca(u , v)} = k\) 且 \(dep_u + dep_v - 2 × dep_{lca(u , v)}\) 尽可能小
假设当前根节点为 \(rt\) ,那么对于子节点 \(x\) 的来说,能与它构成满足上述式子的 \(y\) 可能来自 \(rt\) 的其它分支,也可能 \(y = rt\)
(相同分支内的节点答案的在根节点为 \(rt\) 的子节点的时候就已经算过了)
于是我们可以定义 \(mp_d\) 表示当前距离\(1\)号点距离为 \(d\) 的节点的最小深度 , 定义\(c = k+2 * dis_{rt} - dis{x}\)
那么 \(x\) 提供的贡献就是 \(mp_c + dep_x - 2 * dep_{rt}\)
因为我们计算两点的简单路径权值和、两点简单路径的边的个数是根据它们的\(lca\),所以一个分支内的节点不能相互影响
所以需要先对一个分支统计完贡献后,再添加它的信息
(事实上 \(rt\) 的任意一个分支内的贡献在统计子节点为根的子树中就已经计算过了,
上文也已经讲过了)
AC_Code
#include<bits/stdc++.h>
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define int long long
using namespace std;
const int N = 3e5 + 10;
struct Edge{
int nex , to , w;
}edge[N << 1];
int head[N] , TOT;
void add_edge(int u , int v , int w)
{
edge[++ TOT].nex = head[u] ;
edge[TOT].to = v;
edge[TOT].w = w;
head[u] = TOT;
}
map<int , int>mp;
int n , k , ans = 0x3f3f3f3f;
int hson[N] , HH , sz[N] , dep[N] , dis[N];
void dfs(int u , int far)
{
sz[u] = 1;
dep[u] = dep[far] + 1;
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to , w = edge[i].w;
if(v == far) continue ;
dis[v] = w + dis[u];
dfs(v , u);
sz[u] += sz[v];
if(sz[v] > sz[hson[u]]) hson[u] = v;
}
}
void change(int u , int far)
{
if(mp.count(dis[u])) mp[dis[u]] = min(mp[dis[u]] , dep[u]);
else mp[dis[u]] = dep[u];
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far || v == HH) continue ;
change(v , u);
}
}
void calc(int u , int far , int rt)
{
int x = k + 2 * dis[rt] - dis[u];
if(mp.count(x)) ans = min(ans , mp[x] + dep[u] - 2 * dep[rt]);
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far || v == HH) continue ;
calc(v , u , rt);
}
}
void dsu(int u , int far , int op)
{
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far || v == hson[u]) continue ;
dsu(v , u , 0);
}
if(hson[u]) dsu(hson[u] , u , 1) , HH = hson[u];
int x = k + dis[u];
if(mp.count(x)) ans = min(ans , mp[x] + dep[u] - 2 * dep[u]);
mp[dis[u]] = dep[u];
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to , w = edge[i].w;
if(v == far || v == HH) continue ;
calc(v , u , u) , change(v , u);
}
HH = 0;
if(!op) mp.clear();
}
signed main()
{
cin >> n >> k;
rep(i , 1 , n - 1)
{
int u , v , w;
cin >> u >> v >> w;
u ++ , v ++ ;
add_edge(u , v , w) , add_edge(v , u , w);
}
dfs(1 , 0);
dsu(1 , 0 , 0);
if(ans == 0x3f3f3f3f) cout << -1 << '\n';
else cout << ans << '\n';
return 0;
}