【BOI2019-Day1T2】Alpine valley(DP+倍增)

题目大意

在山谷中有N个村庄,有N-1条道路将村庄连成一棵树,每条道路有一定长度。第E号村庄为山谷的出口。有S个村庄中有商店,商店可提供补给。
由于山谷中天气恶劣,一条道路会被封死。当你处于某一个村庄中时,得知一条道路已经封死,你想知道自己能否活下去,即对于每一个询问,你需要计算,你是否能走出山谷,如果不能走出,则计算到最近的商店获得补给品所需最短路程。

输入
第一行:N,S,Q,E,即 村庄数,商店数,询问数,出口编号
以下N-1行:A,B,W,表示一条连接村庄A,B的长度为W的道路
以下S行:C,表示商店所在村庄编号
一下Q行:I,R,表示询问:第I号道路封死,你在R号村庄的结果

输出
对每个询问,如果能逃出山谷,输出"escaped"
如果不能,输出最近商店距离
如果不能走到商店,输出"oo"

题解

以出口E为整颗树的根。

先计算每个结点的dfs序编号,可以通过dfs序可知R村庄是否在I号边的子树中,很容易判断是否输出escaped

接下来计算最近商店距离
考虑dp
d p [ u ] dp[u] dp[u]表示结点u的子树中离结点u最近的商店距离。
d e p [ u ] dep[u] dep[u]表示根到结点u的距离。

则,若 v ∈ s o n [ u ] v\isin son[u] vson[u] d p [ u ] = m i n ( d p [ v ] + l e n [ u → v ] ) dp[u]=min(dp[v]+len[u\rightarrow v]) dp[u]=min(dp[v]+len[uv])
距结点u最近商店的距离:
v v v u u u到边 I I I路径上的点
a n s [ u ] = m i n ( d p [ v ] + d e p [ u ] − d e p [ v ] ) = m i n ( d p [ v ] − d e p [ v ] ) + d e p [ u ] ans[u]=min(dp[v]+dep[u]-dep[v])=min(dp[v]-dep[v])+dep[u] ans[u]=min(dp[v]+dep[u]dep[v])=min(dp[v]dep[v])+dep[u]
由此受到启发,可以设 v a l [ u ] = d p [ u ] − d e p [ u ] val[u]=dp[u]-dep[u] val[u]=dp[u]dep[u]
a n s [ u ] = d e p [ u ] − m a x ( v a l [ v ] ) ans[u]=dep[u]-max(val[v]) ans[u]=dep[u]max(val[v])

即问题转换为求 u u u到边 I I I上结点的 v a l val val最大值,且 v a l val val可以预处理。
可以用倍增实现。

代码

我写的丑,考试时居然用树链剖分来做。。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN=100005,MAXLOG=18;
const long long LLF=0x3F3F3F3F3F3F3F3FLL;

int N,S,Q,E;
vector<pair<int,int> > adj[MAXN];
int dfc;
int fa[MAXN],len[MAXN],siz[MAXN],st[MAXN],ed[MAXN],nd[MAXN],son[MAXN],tp[MAXN];
long long dep[MAXN],dp[MAXN];
bool shop[MAXN];
int edges[MAXN][3];

void dfs1(int u)
{
	siz[u]=1;
	for(int i=0;i<(int)adj[u].size();i++)
	{
		int v=adj[u][i].first;
		if(v!=fa[u])
		{
			fa[v]=u;
			len[v]=adj[u][i].second;
			dep[v]=dep[u]+len[v];
			dfs1(v);
			siz[u]+=siz[v];
		}
	}
}
void dfs2(int u,int t)
{
	st[u]=++dfc;
	nd[dfc]=u;
	tp[u]=t;
	for(int i=0;i<(int)adj[u].size();i++)
	{
		int v=adj[u][i].first;
		if(v!=fa[u]&&siz[v]>siz[son[u]])
			son[u]=v;
	}
	if(son[u])
		dfs2(son[u],t);
	for(int i=0;i<(int)adj[u].size();i++)
	{
		int v=adj[u][i].first;
		if(v!=fa[u]&&v!=son[u])
			dfs2(v,v);
	}
	ed[u]=dfc;
}
void DP(int u)
{
	dp[u]=dep[u];
	long long tmp=shop[u]?0:LLF;
	for(int i=0;i<(int)adj[u].size();i++)
	{
		int v=adj[u][i].first;
		if(v!=fa[u])
		{
			DP(v);
			tmp=min(tmp,-(dp[v]-dep[v])+adj[u][i].second);
		}
	}
	dp[u]-=tmp;
}

long long mx[MAXN][MAXLOG],lg[MAXN];
void InitST()
{
	for(int i=1;i<=N;i++)
		mx[i][0]=dp[nd[i]];
	for(int j=1;(1<<j)<=N;j++)
		for(int i=1;i+(1<<j)-1<=N;i++)
			mx[i][j]=max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
	for(int i=2;i<=N;i++)
		lg[i]=lg[i/2]+1;
}
long long GetMax(int l,int r)
{
	int t=lg[r-l+1];
	return max(mx[l][t],mx[r-(1<<t)+1][t]);
}
long long solve(int u,int v)
{
	long long ret=dep[u],tmp=-LLF;
	while(dep[tp[u]]>=dep[v])
	{
		tmp=max(tmp,GetMax(st[tp[u]],st[u]));
		u=fa[tp[u]];
	}
	if(dep[u]>=dep[v])
		tmp=max(tmp,GetMax(st[v],st[u]));
	ret-=tmp;
	return ret;
}

int main()
{
	freopen("valley.in","r",stdin);
	freopen("valley.out","w",stdout);
	
	scanf("%d%d%d%d",&N,&S,&Q,&E);
	for(int i=1;i<N;i++)
	{
		scanf("%d%d%d",&edges[i][0],&edges[i][1],&edges[i][2]);
		adj[edges[i][0]].push_back(make_pair(edges[i][1],edges[i][2]));
		adj[edges[i][1]].push_back(make_pair(edges[i][0],edges[i][2]));
	}
	for(int i=1,u;i<=S;i++)
	{
		scanf("%d",&u);
		shop[u]=true;
	}
	dfs1(E);
	dfs2(E,E);
	for(int i=1;i<N;i++)
		if(fa[edges[i][1]]==edges[i][0])
			swap(edges[i][0],edges[i][1]);
	DP(E);
	InitST();
	while(Q--)
	{
		int I,R;
		scanf("%d%d",&I,&R);
		if(st[edges[I][0]]<=st[R]&&st[R]<=ed[edges[I][0]])
		{
			long long ans=solve(R,edges[I][0]);
			if(ans>=LLF)
				puts("oo");
			else
				printf("%lld\n",ans);
		}
		else
			puts("escaped");
	}
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值