[NOIP2015 提高组] 运输计划
简化题意
给定一棵具有\(n\)个节点的树,要求使某一条边权值边为0时,\(m\)个\(u\)到\(v\)的路径长度的最大值最小。\(100 \le n \le300000,1 \le\ m \le300000\)
思路
首先,求\(u\)到\(v\)的路径长度肯定可以用到树剖和线段树。
其次因为要使最大值最小,所以我们不难想出二分答案的思路,但是本蒟蒻不会。
于是就想出了一个比较有意思的贪心做法。
我们可以知道,若想要最大值最小,那么我们所改变的一定是位于长度最大的路径的那条边,否则不会对答案产生任何贡献。
一个暴力解法是枚举每条边将其权值修改为0,然后依次查询所有路径长度的最大值,更新答案的最小值,再把这条边的权值改过来,最后一定能得到正确结果。撑死能拿60。
而实际上我们可以将所有的长度和编号放入一个递减的优先队列,修改后每次将其取出,计算改变之后的长度,若改变之后的长度小于原来的长度,就\(pop\),若改变之后的长度仍等于原来的长度,就可以终止循环更新答案。
为什么这样写是对的呢?
不难想到一条边的权值改变了,可能会使到其他询问的长度变小,假使我们在循环过程中遇到一个长度不变的,那么在它队列后面的所有的长度无论是减少还是不变都不会影响它的长度比后面都大(因为是递减队列),所以后面的长度我们都可以弃掉不用,只关注之前的那一部分即可,更新完答案后再把所有\(pop\)掉的元素再按原值\(push\)进去。
然后我们就可以愉快地A过此题了(不吸氧时2.53s,吸了氧之后1.13s,都可以过)
话说这种做法可能会被某些奇奇怪怪的数据(比如所有路径都相同)卡掉?...
代码
#include <bits/stdc++.h>
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int maxn=350000,inf=2147483411;
int n,m,cnt,tot,maxx,ls,rs,op;
int f[maxn],head[maxn],dep[maxn],son[maxn];
int qu[maxn],ru[maxn],zoms[maxn],s[maxn];
int top[maxn],dfn[maxn],siz[maxn],w[maxn],val[maxn];
struct ED{
int next,to,w;
}e[maxn<<1];
struct EE{
int l,r,sum,maxx;
}tr[maxn<<2];
struct node{
int id,p;
node(){}
node(int x,int y){
id=x;p=y;
}
bool operator < (const node &A)const{
return p<A.p;
}
};
priority_queue<node> q;
//最短完成时间当然取决于最长完成时间
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
//以下是树剖
void find_heavy(int u,int depth){
son[u]=top[u]=0;
siz[u]=1;
dep[u]=depth;
int maxsiz=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,ws=e[i].w;
if(dep[v]) continue;
find_heavy(v,depth+1);
siz[u]+=siz[v];f[v]=u;w[v]=ws;
if(maxsiz<siz[v]){
son[u]=v;
maxsiz=siz[v];
}
}
}
void dfs(int u,int anc){
top[u]=anc;
dfn[u]=++tot;
val[tot]=w[u];
if(son[u]) dfs(son[u],anc);
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==f[u]||v==son[u]) continue;
dfs(v,v);
}
}
//以下是线段树
inline void pushup(int rt){
tr[rt].sum=tr[lson].sum+tr[rson].sum;
return;
}
void build(int rt,int l,int r){
tr[rt].l=l;
tr[rt].r=r;
if(l==r){
tr[rt].sum=val[l];
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
return;
}
void modifys(int rt,int pos,int vals){
if(tr[rt].l==tr[rt].r){
tr[rt].sum=vals;
return;
}
int mid=(tr[rt].l+tr[rt].r)>>1;
if(pos<=mid) modifys(lson,pos,vals);
else modifys(rson,pos,vals);
pushup(rt);
}
int getsum(int rt,int l,int r){
if(l<=tr[rt].l&&tr[rt].r<=r) return tr[rt].sum;
int mid=(tr[rt].l+tr[rt].r)>>1;
int sum=0;
if(l<=mid) sum+=getsum(lson,l,r);
if(r>mid) sum+=getsum(rson,l,r);
return sum;
}
//查询长度
int querysum(int u,int v){
int sum=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
sum+=getsum(1,dfn[top[u]],dfn[u]);
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
sum+=getsum(1,dfn[u]+1,dfn[v]);
return sum;
}
//核心部分
void solve(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
for(int i=dfn[top[u]];i<=dfn[u];++i){
int st=-1;
modifys(1,i,0);
op=0;
while(!q.empty()){
int pos=q.top().id,old=q.top().p;
int now=querysum(qu[pos],ru[pos]);
if(now<old){
s[++op]=pos;
q.pop();
st=max(st,now);
}else{
st=max(st,now);
break;
//长度不变则终止循环
}
}
for(int j=1;j<=op;++j) q.push(node(s[j],zoms[s[j]]));
modifys(1,i,val[i]);
//改回原值
maxx=min(st,maxx);
}
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
for(int i=dfn[u]+1;i<=dfn[v];++i){
int st=-1;
modifys(1,i,0);
op=0;
while(!q.empty()){
int pos=q.top().id,old=q.top().p;
int now=querysum(qu[pos],ru[pos]);
if(now<old) {
s[++op]=pos;
q.pop();
st=max(st,now);
}else{
st=max(st,now);
break;
}
}
for(int j=1;j<=op;++j) q.push(node(s[j],zoms[s[j]]));
modifys(1,i,val[i]);
maxx=min(st,maxx);
}
}
int main(){
n=read();m=read();maxx=-inf;
for(int i=1;i<=n-1;++i){
int x=read(),y=read(),t=read();
add(x,y,t);add(y,x,t);
}
find_heavy(1,1);
dfs(1,1);
build(1,1,tot);
for(int i=1;i<=m;++i){
qu[i]=read(),ru[i]=read();
zoms[i]=querysum(qu[i],ru[i]);
q.push(node(i,zoms[i]));
if(zoms[i]>maxx){
maxx=zoms[i];
ls=qu[i],rs=ru[i];
//记录长度最大值
}
}
solve(ls,rs);
printf("%d",maxx);
return 0;
}