noip2012 图论+贪心+二分

2 篇文章 0 订阅
2 篇文章 0 订阅

首先,这道题是有二分性质的,就是说时间放的越长,能切断的点就越多。然后就是贪心验证。
刚开始这样想的——对于每一次验证,按军队深度(与根之间的距离)从排序后,
1.一个点在给定时间内跑不到第二层,就尽量往上跑。
2.如果跑到第二层,就填上这一层。
3.如果跑到第二层,发现这点填过了,就跑到根,记录一下剩余时间。
这样之后bfs,没有填过的第2层由跑到根的点补一下。
调了一天,发现思路错了。
因为我可以一个点去补其他点,再让其他点补回来。

4
1 2 3
1 3 2
1 4 7
3
2 2 3

答案应该是9,3号点堵4号,2号堵3号。
正解是如果一只军队可以与自己那条路径上的那个第二层根节点(A)配对,且这个军队如果到达根便无法返回A,那么就让他驻扎在A。证:如果这只军队去了别的地方x点,然后y军队来填补A,由已知得w(A)>W(x)显然让y去x,这支军队驻扎在A最好。

#include<iostream>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cstring>
#include<algorithm>
#define maxn 50005
#define LL long long
using namespace std;
int n,m;
struct E{
	int to,nxt;
	LL d;
}b[maxn*2];
int fst[maxn],tot;
void insert(int f,int t,LL d)
{
	b[++tot]=(E){t,fst[f],d};fst[f]=tot;
	b[++tot]=(E){f,fst[t],d};fst[t]=tot;
}
LL dis[maxn];
int fa[maxn][20];
int top[maxn];
int pe[maxn];
bool cmp(int a,int b)
{
	if(dis[a]!=dis[b])
		return dis[a]>dis[b];
	return a<b;
}
bool isf[maxn];
int sz[maxn];
void dfs(int x,int p)
{
	top[x]=p;
	for(int i=1;i<=16;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=fst[x];i;i=b[i].nxt)
	{
		int v=b[i].to;
		if(!fa[v][0])
		{
			fa[v][0]=x;
			dis[v]=dis[x]+b[i].d;
			dfs(v,p);sz[x]++;
		}
	}
	if(!sz[x])isf[x]=true;
}
bool vis[maxn];
int nd[maxn],C;
queue<int> Q;
void bfs(LL mid)
{
	Q.push(1);C=0;vis[1]=1;
	while(!Q.empty())
	{
		int u=Q.front();Q.pop();
		for(int i=fst[u];i;i=b[i].nxt)
		{
			int v=b[i].to;
			if(!vis[v])
			{
				if(isf[v])
					nd[++C]=top[v];
				else Q.push(v);
				vis[v]=1;
			}
		}
	}
	sort(nd+1,nd+C+1,cmp);
	C=unique(nd+1,nd+C+1)-nd-1;
}
LL stack[maxn];int Top;
bool check(LL mid)
{
	memset(vis,0,sizeof(vis));Top=0;
	for(int i=1;i<=m;i++)
	{
		int u=pe[i];int v=top[u];
		LL cst=dis[u]-dis[v];
		if(vis[v]||dis[u]+dis[v]<=mid)
		{
			LL p=mid-dis[u];
			stack[++Top]=p;
		}
		else if(cst<=mid)vis[v]=1;
		else
		{
			int p=u;LL res=mid;
			for(int j=16;j>=0;j--)
			{
				int w=fa[p][j];
				LL wv=dis[p]-dis[w];
				if(wv<=res){p=w;res-=wv;}
			}
			while(sz[fa[p][0]]==1&&!vis[p]) 
				vis[p]=1,p=fa[p][0];
			vis[p]=1;
		}
	}
	bfs(mid);
	int H=1;
	for(int i=C;i>=1;i--)
	{
		LL p=dis[nd[i]];
		while(stack[H]<p&&H<=Top)H++;
		if(H>Top) return false;H++;
	}
	return true;
}
int main()
{
	scanf("%d",&n);
	int u,v;LL d;
	int cnt=0;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&d);
		insert(u,v,d);
		if(u==1||v==1) cnt++;
	}
	for(int i=0;i<=18;i++)fa[1][i]=1;
	for(int i=fst[1];i;i=b[i].nxt)
	{
		int v=b[i].to;
		dis[v]=b[i].d;
		fa[v][0]=1;
		dfs(v,v);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
		scanf("%d",&pe[i]);
	sort(pe+1,pe+m+1,cmp);
	if(m<cnt)
	{
		puts("-1");
		return 0;
	}
	LL l=-1,r=1e9,mid;
	while(r-l>1)
	{
		mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid;
	}
	cout<<r<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值