[NOIP2015] 运输计划

S o l u t i o n : Solution: Solution:

Subtask 1 : 20 p t s \text{Subtask 1}: 20pts Subtask 1:20pts

m = 1 m=1 m=1

好像很可做
O ( n ) O(n) O(n)暴力一下找路径上最长的一条边删去即可

Subtask 2 : 30 p t s \text{Subtask 2}:30pts Subtask 2:30pts
暴力删边,暴力跑 O ( n 2 m ) O(n^2m) O(n2m),应该能比 Subtask 1 \text{Subtask 1} Subtask 1多拿两个点#2,#3
就是25分到手

Subtask 3 : 50 p t s \text{Subtask 3}:50pts Subtask 3:50pts
我们还是暴力删边,但是维护树上路径,我们想到了树剖,所以用树剖维护一下 O ( n m l o g 2 n ) O(nmlog^2n) O(nmlog2n)
应该可以拿到#1-6,#8-9,#11-12一共10个点
加油, t g tg tg d 2 t 3 d2t3 d2t3我们已经拿到一半了

Subtask 4 : 55 p t s \text{Subtask 4}:55pts Subtask 4:55pts
我们看之前没过的#7 ,他给出的特殊条件是一条链
一条链就很好搞啦,弄个前缀和维护一下,还是暴力删边,但是每次换删哪条边的时候需要重新跑一边前缀和,复杂福是 O ( n ⋅ ( n + m ) ) O(n\cdot (n+m)) O(n(n+m)),但是只能过#7,不能过下面的那四个链的点

Subtask 5 : 75 p t s \text{Subtask 5}:75pts Subtask 5:75pts
我们发现,对于链的情况,其实我们就是要维护区间和, 还有单点修改,所以我们想到了线段树!
我们用线段树维护链上的区间和,这样 O ( m l o g n ) O(mlogn) O(mlogn)我们就可以把所有链的情况都过了

Subtask 6 : 100 p t s \text{Subtask 6}:100pts Subtask 6:100pts
上面说了一些 Subtask \text{Subtask} Subtask的做法,现在来说一种正解
话说洛谷题解里好像没人写这种做法
题目里面说到

如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?

所以题目让我们求的是所有航线中最长的那条的最小值
所以我们想到了二分,但是二分其实并不好做(可以二分+lca+树上差分,复杂度是 O ( m l o g n l o g ∑ t i ) O(mlognlog\sum t_i) O(mlognlogti))在 n n n特别大的时候其实复杂度并不是很优秀

我们再回头看一眼题,是一个树上的问题,没有动态删边连边的操作,排除了 l c t lct lct,那么处理树上问题最常见的方法就是树链剖分了
所以我们把他树剖一下

然后我们考虑最后的答案,我们删的边一定是最长的 k k k条路径上的交集(最长的 k k k条路径一定都经过我们删的哪条边)中的最长的一条,为什么呢?比如说第二长的我们没有选,那么答案一定不会比第二长的路径的长度短(性质1)

然后我们又注意到一条性质,当第 k k k长的边没有覆盖的时候,我们再要求删掉的边在第 k + 1 k+1 k+1长的路径上的时候是没有意义的,因为约束条件变多一定不会比此前的答案更优(性质2)

考虑答案是什么,当我们找出了同时覆盖前 k k k大路径的边中最长的那个作为删掉的边,那么答案有可能是
1.最长的路径的长度-删掉的边的长度
2.第 k + 1 k+1 k+1条路径的最大值
而对于第 2 2 2- k k k长的路径和答案一定是无关的
那么这个时候答案就是1,2取个 max ⁡ \max max

所以我们形成了一个初步的思路,就是首先树剖一下,求出每个路径的长度,为了便于处理,我们按路径长度降序排序 i i i从1到m循环,根据上面的算出答案。当没有一条边满足同时位于前 k k k大路径的时候,根据性质1,2,直接break掉就可以了

那么我们现在需要做的是
1.求路径长度
2.判断是否有一条路径能够满足同时被前 i i i长的路径所覆盖
3.求满足条件的中最长的

对于1. ,树剖板子
对于2. ,我们可以另开一个计数器 t i m tim tim表示这个点被覆盖了多少次,我们每次新加入一条路径的时候,我们把这条路径上的 t i m tim tim都+1,判断一下这条路径上的最大值,如果等于 i i i,说明有,如果小于 i i i,那么就是没有
对于3. ,我们可以再开一个变量储存所有被覆盖的次数等于tim的边的边权最大值,那么稍微改一下pushup就可以了

void pushup(int u){
	seg[u].val=seg[lc].val+seg[rc].val;
	if(seg[lc].tim>seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=seg[lc]._max;
	if(seg[rc].tim>seg[lc].tim)seg[u].tim=seg[rc].tim,seg[u]._max=seg[rc]._max;
	if(seg[lc].tim==seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=max(seg[lc]._max,seg[rc]._max);
}

但是我们查询2,3应该同时进行,所以我们需要返回一个结构体类型,我是为了节省空间用的pair,要不然我就像往常 一样 直接 用线段树的结构体了qaq

这个做法的复杂度应该是 O ( n + n + n + n + m l o g 2 n + m l o g m + m l o g 2 n ) = O ( m l o g 2 n ) O(n+n+n+n+mlog^2n+mlogm+mlog^2n)=O(mlog^2n) O(n+n+n+n+mlog2n+mlogm+mlog2n)=O(mlog2n)
(输入边+dfs1+dfs2+build+查询边长+排序+查询)

n,m都是 3 × 1 0 5 3\times10^5 3×105的时候是不用像之前说的二分那样去卡常的
而且这种做法除了代码长点也没有什么细节,唯一的细节就是答案怎么算啦,而且长也主要是正常的板子啦

上代码(总1.89s):

# include <bits/stdc++.h>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define RepG(i,u) for(int i=head[u];~i;i=e[i].next)
# define debug puts("QAQ");

typedef long long ll;
const int N=3e5+5;
const int mod=1e9+7;
const double eps=1e-7;

template <typename T> void read(T &x){
	x=0;int f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
	x*=f;
}

int n,m,ans=INT_MAX;
int head[N],cnt;
int faz[N],son[N],dep[N],siz[N],dfn[N],top[N],tot;
int a[N],_a[N];

struct Edge{
	int to,next,w;	
}e[N<<1];

struct querys{
	int x,y,t;
	bool operator < (const querys &cmp)const{
		return t>cmp.t;
	}
}q[N];

void add(int x,int y,int c){
	e[++cnt]=(Edge){y,head[x],c},head[x]=cnt;	
}

struct segment_tree{
	int l,r;
	int val,tim,_max;//val表示这一区间的长度,tim表示这条边被标记过几次,_max表示这个区间内被标记过tim次的边的最长权值 
	int tag;	
}seg[N<<2];

# define lc (u<<1)
# define rc (u<<1|1)

void pushup(int u){
	seg[u].val=seg[lc].val+seg[rc].val;
	if(seg[lc].tim>seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=seg[lc]._max;
	if(seg[rc].tim>seg[lc].tim)seg[u].tim=seg[rc].tim,seg[u]._max=seg[rc]._max;
	if(seg[lc].tim==seg[rc].tim)seg[u].tim=seg[lc].tim,seg[u]._max=max(seg[lc]._max,seg[rc]._max);
}

void pushdown(int u){
	seg[lc].tim+=seg[u].tag;
	seg[rc].tim+=seg[u].tag;
	seg[lc].tag+=seg[u].tag;
	seg[rc].tag+=seg[u].tag;
	seg[u].tag=0;	
}

pair<int,int> merge(pair<int,int> l,pair<int,int> r){
	if(l.first>r.first)return l;
	if(r.first>l.first)return r;
	if(l.first==r.first)return make_pair(l.first,max(l.second,r.second));
}

void build(int u,int l,int r){
	seg[u].l=l,seg[u].r=r;
	if(l==r){
		seg[u].val=seg[u]._max=_a[l];
		seg[u].tim=0;
		return;
	}
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	pushup(u);
}

void update(int u,int l,int r,int k){
	if(seg[u].l>=l&&seg[u].r<=r){
		seg[u].tim++;
		seg[u].tag++;
		return;
	}
	if(seg[u].tag)pushdown(u);
	int mid=seg[u].l+seg[u].r>>1;
	if(l<=mid)update(lc,l,r,k);
	if(r>mid)update(rc,l,r,k);
	pushup(u);
}

int Getlen(int u,int l,int r){
	if(seg[u].l>=l&&seg[u].r<=r)return seg[u].val;
	if(seg[u].tag)pushdown(u);
	int mid=seg[u].l+seg[u].r>>1;
	int res=0;
	if(l<=mid)res+=Getlen(lc,l,r);
	if(r>mid)res+=Getlen(rc,l,r);
	return res;	
}

pair<int,int> query(int u,int l,int r){
	if(seg[u].l>=l&&seg[u].r<=r)return make_pair(seg[u].tim,seg[u]._max);
	if(seg[u].tag)pushdown(u);
	int mid=seg[u].l+seg[u].r>>1;
	if(r<=mid)return query(lc,l,r);
	if(l>mid)return query(rc,l,r);
	return merge(query(lc,l,r),query(rc,l,r));	
}

void RouteModify(int x,int y,int k){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,dfn[top[x]],dfn[x],k);
		x=faz[top[x]];
	}
	if(x!=y){
		if(dep[x]>dep[y])swap(x,y);
		update(1,dfn[x]+1,dfn[y],k);	
	}
}

int RouteQuerylen(int x,int y){
	int res=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		res+=Getlen(1,dfn[top[x]],dfn[x]);
		x=faz[top[x]];	
	}
	if(x!=y){
		if(dep[x]>dep[y])swap(x,y);
		res+=Getlen(1,dfn[x]+1,dfn[y]);	
	}
	return res;
}

pair<int,int> RouteQuery(int x,int y){
	pair<int,int> res;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		res=merge(res,query(1,dfn[top[x]],dfn[x]));
		x=faz[top[x]];
	}
	if(x!=y){
		if(dep[x]>dep[y])swap(x,y);
		res=merge(res,query(1,dfn[x]+1,dfn[y]));	
	}
	return res;
}

void dfs1(int u,int fa){
	faz[u]=fa;
	siz[u]=1;
	dep[u]=dep[fa]+1;
	RepG(i,u){
		int v=e[i].to;
		if(v==fa)continue;
		a[v]=e[i].w;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;	
	}
}

void dfs2(int u,int _top){
	top[u]=_top;
	dfn[u]=++tot;
	_a[tot]=a[u];
	if(!son[u])return;
	dfs2(son[u],_top);
	RepG(i,u){
		int v=e[i].to;
		if(v==faz[u]||v==son[u])continue;
		dfs2(v,v);
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	read(n),read(m);
	Rep(i,1,n-1){
		int x,y,c;
		read(x),read(y),read(c);
		add(x,y,c),add(y,x,c);
	}
	dfs1(1,0),dfs2(1,1);
	build(1,1,n);
	Rep(i,1,m){
		read(q[i].x),read(q[i].y);
		q[i].t=RouteQuerylen(q[i].x,q[i].y);
	} 
	sort(q+1,q+m+1);
	ans=q[1].t;
	Rep(i,1,m){
		RouteModify(q[i].x,q[i].y,1);
		pair<int,int> res=RouteQuery(q[i].x,q[i].y);
		if(res.first<i)
			break;
		ans=min(ans,max(q[1].t-res.second,q[i+1].t));
	}
	printf("%d\n",ans);
	return 0;
}

第一道自己写的 d a y 2 T 3 day2T3 day2T3耶,开森

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值