不得不说,这真是一道优(s)质(b)图(卡)论(常)题.相信它能给拼搏于逐梦之路上的你有力的援助,让你感受到出题人的满满善意(大雾)
Solution [NOIP2015]运输计划
题目大意:给定一棵树,以及(m)条简单路径,你可以使任意一条边的权值变为(0),询问最大路径长度的最小值
二分答案,树上差分
分析:假设出题人良心发现不卡常,我们看看怎么做
"最大值最小"我们可以考虑二分
首先我们把路径长度求出来(这玩意儿可以树上差分 / 倍增),然后对其按升序排序
差分做法:设有两点(u,v),(LCA)为(l),(dis[u])为(u)到根节点路径长度,则路径((u,v))长为(dis[u] + dis[v] - 2 imes dis[l])
假设我们二分一个答案(limit),我们可以很快的把所有长度(dis > limit)的路径找出来,假设这样的路径有(num)个
然后我们找被(num)个路径共同经过的边,如果没有,那么可以判(limit)这个答案为(false)(因为无论你怎么改总有路径长度(>limit))
然后假如有多条这样边,贪心选取其中权值最大的,看一下长度最大的路径减去这个边权后长度是否(leq limit)即可
于是问题变成了把一段路径上所有边权增加(1),询问一条边的边权,我们可以考虑把边权下放到深度较深的点,然后继续树上差分
假如要修改(u,v)的话,我们把(sum[u]+1,sum[v]+1,sum[l]-2),然后子树和就是点权(即为边权)
下面就是喜闻乐见的卡常时间
- (1.)发现求(LCA)次数较多,而且可以离线做,考虑用(Tarjan)算法优化
- (2.)读入优化,配合(fread)食用更佳
- (3.)子树求和,求出树的(dfs)序,然后倒着用(dp)刷表法思想刷新父亲,因为儿子一定比父亲晚出现,这个优化让我最大数据点从(1400ms+)到(327ms)……
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 3e5 + 100;
namespace FastIO{
typedef int type;
const int bufsize = 1 << 20;
char buf[bufsize],*s,*t;
inline char gc(){
if(s == t)s = buf,t = buf + fread(buf,sizeof(char),bufsize,stdin);
return *(s++);
}
inline type read(){
type ret = 0;char c = gc();
while(c < '0' || c > '9')c = gc();
while(c >= '0' && c <= '9'){
ret = ret * 10 + c - '0';
c = gc();
}
return ret;
}
}using FastIO::read;
struct Ques{int v,id;}ques[maxn << 1];
struct Edge{int to,dist;}Edges[maxn << 1];
int head[maxn],nxt[maxn << 1],headq[maxn],nxtq[maxn << 1];
inline void addques(int u,int v,int id){
static int tot;
ques[++tot] = Ques{v,id};
nxtq[tot] = headq[u];
headq[u] = tot;
}
inline void addedge(int from,int to,int dist){
static int tot;
Edges[++tot] = Edge{to,dist};
nxt[tot] = head[from];
head[from] = tot;
}
struct Road{int u,v,lca,val;}road[maxn];
int f[maxn],dfn[maxn],rnk[maxn],faz[maxn],sum[maxn],val[maxn],n,m,dis[maxn],l,r,ans,dfs_tot;
inline int find(int x){return x == f[x] ? x : f[x] = find(f[x]);}
inline void dfs_init(int u = 1){
f[u] = u;
dfn[u] = ++dfs_tot;
rnk[dfn[u]] = u;
for(int i = head[u];i;i = nxt[i]){
const Edge &e = Edges[i];
if(dfn[e.to])continue;
dis[e.to] = dis[u] + e.dist;
val[e.to] = e.dist;
faz[e.to] = u;
dfs_init(e.to);
f[e.to] = u;
}
for(int i = headq[u];i;i = nxtq[i]){
const Ques &q = ques[i];
if(dfn[q.v])road[q.id].lca = find(q.v);
}
}
inline int query(int u,int v,int l){return dis[u] + dis[v] - 2 * dis[l];}
inline void modify(int u,int v,int l){sum[u]++,sum[v]++,sum[l] -= 2;}
inline int max(int a,int b){return a > b ? a : b;}
inline bool check(int limit){
if(road[m].val <= limit)return true;
memset(sum,0,sizeof(sum));
int now = 0,mx = -1;
for(int i = m;i >= 1 && road[i].val > limit;i--,now++)
modify(road[i].u,road[i].v,road[i].lca);
for(int i = n;i >= 1;i--)
sum[faz[rnk[i]]] += sum[rnk[i]];
for(int i = 1;i <= n && road[m].val - mx > limit;i++)
if(sum[i] == now)mx = max(mx,val[i]);
return road[m].val - mx <= limit;
}
int main(){
n = read(),m = read();
for(int u,v,w,i = 1;i < n;i++)
u = read(),v = read(),w = read(),addedge(u,v,w),addedge(v,u,w);
for(int i = 1;i <= m;i++)
road[i].u = read(),road[i].v = read(),addques(road[i].u,road[i].v,i),addques(road[i].v,road[i].u,i);
dfs_init();
for(int i = 1;i <= m;i++)
road[i].val = query(road[i].u,road[i].v,road[i].lca),r = max(r,road[i].val);
sort(road + 1,road + 1 + m,[](const Road &a,const Road &b){return a.val < b.val;});
int tot = 0;
while(l <= r){
int mid = (l + r) >> 1;
tot++;
if(check(mid))ans = mid,r = mid - 1;
else l = mid + 1;
}
printf("%d
",ans);
return 0;
}