NOIP2015运输计划的一个O(n)解法

一个O(n)的解法。

不难发现有如下性质:

  • 被改造成虫洞的边一定在最长路径上

那么,我们用类似提直径的方法把这条路径给拎出来

就会形成这样的一棵树。

那么,对于一条边,若其被改成虫洞,最长路径会有如下三种情况:

  • 依然是当前的最长路径

  • 被改造边左端点的最长路径

  • 被改造边右端点的最长路径

难道我们不用考虑经过该边的路径吗??

当然不用。

我们已经是在最长路径上改造边了,而最长路径是长于任何一条路径的(废话),那么经过该边的任何路径在此边被改造后依然短于最长路径,故不用考虑。

至于求两端的最长路径,我们可以前缀和后缀分别维护一下。


那么怎么求前缀(后缀)呢?

我们可以在每个点开个vector,对于一条路径,我们把另一端点以及路径的编号加到vector中。

然后,再开个vis数组,表示这个点是否被访问过。

在遍历时,我们访问所有以这一点为一端点的路径标号\(id\)和另一端点\(y\),若另\(vis[y]=true\),就拿\(id\)的长度去更新即可。(口胡不清,这最好手动模拟一下)

至于求路径长和LCA呢?

额,树剖我是当常数看的。。。

实在不行你可以Tarjan离线预处理啊

代码(巨丑无比):

#include<bits/stdc++.h>
#define reg register int
#define MAXN 300010
using namespace std;
int n,m,head[MAXN],dis[MAXN],tot,maxx,bh,pre[MAXN],sumfr[MAXN],sumla[MAXN],num[MAXN],lian[MAXN],ans=2e9+7;
bool mark[MAXN],vis[MAXN];
struct node {
	int st,ed,lca,d;
} P[MAXN];
struct Edge {//前向星
	int ed,v,last;
} G[MAXN*2];
struct que {
	int ed,id;
};
vector<que> Q[MAXN];
struct s__ {//树剖预处理
	int son[MAXN],size[MAXN],top[MAXN],deep[MAXN];
	void DFS1(int x,int fa,int v) {
		dis[x]=dis[fa]+v;
		pre[x]=fa;
		deep[x]=deep[fa]+1;
		size[x]=1;
		for(int i=head[x]; ~i; i=G[i].last) {
			int t=G[i].ed,v=G[i].v;
			if(t==fa)continue;
			DFS1(t,x,v);
			size[x]+=size[t];
			if(size[son[x]]<size[t])son[x]=t;
		}
	}
	void DFS2(int x,int fa,int zu) {
		top[x]=zu;
		if(son[x])DFS2(son[x],x,zu);
		for(int i=head[x]; ~i; i=G[i].last) {
			int t=G[i].ed;
			if(t==son[x]||t==fa)continue;
			DFS2(t,x,t);
		}
	}
	int LCA(int x,int y) {
		while(top[x]!=top[y]) {
			if(deep[top[x]]<deep[top[y]])swap(x,y);
			x=pre[top[x]];
		}
		if(deep[x]>deep[y])swap(x,y);
		return x;
	}
} shupou;
void Rd(int &res) {//读优
	res=0;
	char ch=getchar();
	while('0'>ch||ch>'9')ch=getchar();
	while('0'<=ch&&ch<='9')res=(res<<3)+(res<<1)+(ch-'0'),ch=getchar();
}
void Add(int st,int ed,int v) {
	tot++;
	G[tot]=Edge {ed,v,head[st]};
	head[st]=tot;
}
void DFS(int x,int fa,int &bb) {
	num[x]=bb;
	for(int i=head[x]; ~i; i=G[i].last) {
		int t=G[i].ed;
		if(t==fa)continue;
		if(!mark[t])continue;
		lian[bb]=G[i].v;
		bb++;
		DFS(t,x,bb);
	}
}
void DFSla(int x,int fa,int zu) {//后缀
	vis[x]=1;
	for(int i=0; i<Q[x].size(); i++) {//访问vector
		int t=Q[x][i].ed,id=Q[x][i].id;
		if(vis[t]==1)sumla[zu]=max(sumla[zu],P[id].d);//更新
	}
	int nex=0;
	for(int i=head[x]; ~i; i=G[i].last) {
		int t=G[i].ed,v=G[i].v;
		if(t==fa)continue;
		if(mark[t]) {//优先遍历两旁伸出的子树
			nex=t;
			continue;
		}
		DFSla(t,x,zu);
	}
	if(nex) {
		sumla[num[nex]]=sumla[num[x]];//更新下一个的后缀
		if(nex==P[bh].st)return;//下一个点如果是另一端点的话就直接退出
		DFSla(nex,x,num[nex]);
	}
}
void DFSfr(int x,int fa,int zu) {//前缀
	vis[x]=1;
	for(int i=0; i<Q[x].size(); i++) {//访问vector
		int t=Q[x][i].ed,id=Q[x][i].id;
		if(vis[t]==1)sumfr[zu]=max(sumfr[zu],P[id].d);//更新
	}
	int nex=0;
	for(int i=head[x]; ~i; i=G[i].last) {
		int t=G[i].ed,v=G[i].v;
		if(t==fa)continue;
		if(mark[t]) {//优先遍历两旁伸出的子树
			nex=t;
			continue;
		}
		DFSfr(t,x,zu);
	}
	if(nex) {
		sumfr[num[nex]]=sumfr[num[x]];//更新下一个的前缀
		if(nex==P[bh].ed)return;//下一个点如果是另一端点的话就直接退出
		DFSfr(nex,x,num[nex]);
	}
}
int main() {
	memset(head,-1,sizeof(head));
	Rd(n),Rd(m);
	for(int i=1; i<=n-1; i++) {
		int x,y,z;
		Rd(x),Rd(y),Rd(z);
		Add(x,y,z);
		Add(y,x,z);
	}
	shupou.DFS1(1,0,0);
	shupou.DFS2(1,0,1);
	for(int i=1; i<=m; i++) {
		Rd(P[i].st),Rd(P[i].ed);
		P[i].lca=shupou.LCA(P[i].st,P[i].ed);
		P[i].d=dis[P[i].st]+dis[P[i].ed]-2*dis[P[i].lca];
		if(P[i].d>maxx)maxx=P[i].d,bh=i;
		Q[P[i].st].push_back(que {P[i].ed,i});
		Q[P[i].ed].push_back(que {P[i].st,i});
	}
	int st=P[bh].st,ed=P[bh].ed,lca=P[bh].lca;
	while(st!=lca)mark[st]=true,st=pre[st];
	while(ed!=lca)mark[ed]=true,ed=pre[ed];
	mark[lca]=true;
	int bb=1;
	DFS(P[bh].st,0,bb);
	DFSfr(P[bh].st,0,num[P[bh].st]);
	memset(vis,false,sizeof(vis));
	DFSla(P[bh].ed,0,num[P[bh].ed]);
	for(int i=1;i<=bb;i++){//更新最终答案
		int res=0;
		res=max(res,P[bh].d-lian[i]);
		res=max(res,sumfr[i]);
		res=max(res,sumla[i+1]);
		ans=min(ans,res);
	}
	cout<<ans;
	return 0;
}
posted @ 2019-08-15 17:09  TieT  阅读(286)  评论(0编辑  收藏  举报