题目大意:
有一棵带权值的树,树上有n个节点,m条路径(树上任意两点的路径唯一)。 现在我们可以使得任意一条边路径长度为0,问我们修改哪条边可以使得最长路径最短。即:
其中d_i是每条边的距离。
解题思路:最大值最小优先考虑使用二分。这里的答案满足 false false true true结构。我们预处理每个路径的长度和LCA。接着我们每次在做二分枚举mid完成 check()函数,check()函数中看哪个路径的长度大于mid,大于mid我们把这条路径上的边权全部加一(原图的边权值先置为0),假设有cnt个路径的长度大于mid。最后我们讨论每条边的权值,若权值等于cnt(表明这cnt条路径都需要用到这条边),我们用最长路径减去这个边的权值,若结果小于 等于 mid返回true. 否则返回false.
这里需要用到一些知识,首先树上路径长度的预处理,LCA的处理。
这里我们使用前向星+tarjan LCA,前向星简介:
前向星就是一个存储图的结构,和邻接表这种结构是参不多的,网上很多讲的很抽象,这里我结合代码(前向星DFS)和我自己的理解来讲。首先我们最容意理解的一种结构是:
通过这种结构我们可以遍历从1号节点出来的所有节点,这应该是我们最常用的功能了(图的遍历以及各种图的算法都需要用到这种结构),现在我们看一下前向星怎么实现这种结构。
首先我们定义一种数据结构.
typedef struct{
int dis,next,to;
}link;
//前向星.
我们再通过增加边的代码,来理解前向星的结构。
///
const int MAXN=1e5+10;
link gra[MAXN];
int head[MAXN];
int cnt=0;
void add_edge(int u,int v,int w){
gra[++cnt].next=head[u];
gra[cnt].to=v;
gra[cnt].dis=w;
head[u]=cnt;
}
首先我们要弄清楚两个关键变量,一个是cnt,一个是u。
cnt承载的信息是最丰富的,通过cnt信息,我们可以直到cnt代表的节点可以走去哪个节点(结构体link中的to决定),去这个节点的长度为多少(结构体中的dis决定),他的兄弟是谁(结构体中的next决定),那么我们怎么获取这个cnt信息呢?这里有一个关键的变量head,通过这个head,我们可以获取某个节点对应的cnt的信息。我们看一下对应的DFS应该怎么写。
int flag[MAXN];
void DFS(int u){
flag[u]=1; //避免重复走
for(int cnt=head[u];cnt;cnt=gra[cnt].next){ //通过head获取cnt信息
//gra[cnt].next 开始遍历它的兄弟,根节点没有兄弟
int to=gra[cnt].to;
int dis=gra[cnt].dis;
if(flag[to])continue;
DFS(to); //去它下一个节点.
}
}
LCA求法很多,之前用倍增T了,网上找了个tarjan的就过了,具体原理我也不懂,所以就不说了。
另外,我们还需要用到树上路径权值加1操作,naive做法会导致复杂度过了,这里需要用树上差分,具体的讲解可以看之前的洛谷第一节课的博客。简要来说对于u,v路径加1,我们
之后用DFS遍历来恢复,即可达到O(n) 所有路径加1的操作。 另外,本题需要用快读。
// Sparse Matrix DP approach to find LCA of two nodes
#include <bits/stdc++.h>
#define OPEN 0
#define RG register
using namespace std;
const int MAXN = 3e5 + 10;
typedef struct{
int to,nx,w;
}gra;
typedef struct{
int u,v,lca,len;
}mys;
int n, m;
mys len[MAXN];
int cnt=0;
gra ori[MAXN];
gra que[MAXN];
int dif[MAXN];
int head[MAXN];
int f[MAXN];
int dis[MAXN];
int maxlen=-1;
bool vis[MAXN];
int back_edge_w[MAXN];
void add_edge(int u,int v,int w=0){
ori[++cnt].to=v;
ori[cnt].nx = head[u];
ori[cnt].w=w;
head[u]=cnt;
}
int mycount;
int qcnt;
int headq[MAXN];
void qadd_edge(int u,int v){
que[++qcnt].nx=head[qcnt];
que[qcnt].to=v;
headq[u]=qcnt;
}
int dis_find(int u){
if(f[u]==u)return u;else return f[u]=dis_find(f[u]);
}
int suc;
int dfs(int u,int prev,int tar){
for(int i=head[u];i;i=ori[i].nx){
int nx=ori[i].to;
if(nx==prev)continue;
dif[u]+=dfs(nx,u,tar);
}
if(dif[u]>=mycount && maxlen - back_edge_w[u]<=tar){
suc=1;
}
return dif[u];
}
bool check(int mid) {
memset(dif, 0, sizeof(dif));
mycount = 0;
for (int i = 1; i<=m; i++) {
if (len[i].len>mid) {
mycount++;
dif[len[i].u] += 1;
dif[len[i].v] += 1;
dif[len[i].lca] -= 2;
}
}
suc=0;
dfs(1,0,mid);
if(suc)return true;
else return false;
}
inline int read()
{
RG char c = getchar(); RG int x = 0;
while (c<'0' || c>'9') c = getchar();
while (c >= '0'&&c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x;
}
void tarjan(int u,int pre) //tarjan������
{
for(int i=head[u];i;i=ori[i].nx){
int v=ori[i].to;
if(v==pre)
continue;
dis[v]=dis[u]+ori[i].w;
tarjan(v,u);
back_edge_w[v]=ori[i].w;
int f1=dis_find(v);
int f2=dis_find(u);
if(f1!=f2)
f[f1]=dis_find(f2);
vis[v]=1;
}
for(int i=headq[u];i;i=que[i].nx)
if(vis[que[i].to])
{
int p=(i+1)>>1;
len[p].lca=dis_find(que[i].to);
len[p].len=dis[u]+dis[que[i].to]-2*dis[len[p].lca];
maxlen=max(maxlen,len[p].len);
}
}
int main()
{
#if OPEN
freopen("vsin.txt", "r", stdin);
#endif
n=read();m=read();
cnt=0;
for(int i=0;i<MAXN;i++)f[i]=i;
for (int i = 0; i<n - 1; i++) {
int a, b, c;
a=read();b=read();c=read();
add_edge(a,b,c);
add_edge(b,a,c);
}
int x = 0; int y = -1;
qcnt=0;
for (int i = 1; i<=m; i++) {
int u=read();
int v=read();
qadd_edge(u,v);
qadd_edge(v,u);
len[i].u=u;
len[i].v=v;
}
tarjan(1,0);
y =maxlen+1;
while (x <y) {
int mid = x + (y - x) / 2;
if (check(mid))y = mid;
else x = mid+1;
}
printf("%d\n",y);
return 0;
}