BZOJ3832: [Poi2014]Rally

111 篇文章 0 订阅
14 篇文章 0 订阅

题目大意:给你一个DAG,让你删掉一个点,使得图中最长路最短


这是一道神题啊,不上网搜题解我是肯定不会做....

首先新建超级源汇,问题就变成了求删掉一个点后,超级源点到超级汇点的最长路最短是多少

首先需要想到,任意的割集都会把至少一条从源到汇的最长路上的边割掉

所以我们可以先计算出源点到所有点的最长路以及所有点到汇点的最长路

然后为每个边赋一个权值,权值=源点到起点的最长路+终点到汇点的最短路

然后我们可以用堆来维护一个删掉某个点之后的割集,然后按照拓扑序来选定删掉的点

一开始把所有点划分成两个集合,只有源点在S集合,其他店在T集合,堆永远维护从S到T且和当前选定要删掉的点x无关系的边的权值,堆中元素的最大值就是删掉当前点后的最长路,(因为当前的最长路至少有一条边在当前割集出现了)

每当更改x时,先把原来的x的所有指向T的出边加入堆,再把从S指向新的x的从堆中删除就可以了


#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#define N 2000010
using namespace std;
int du[N];
int q[N],h,t;
int w[N];
struct ljb
{
	int fr[N],to[N],nxt[N],pre[N>>2],dis[N>>2],du[N>>2],cnt;
	void ae(int ff,int tt)
	{
		cnt++;
		fr[cnt]=ff;
		to[cnt]=tt;
		nxt[cnt]=pre[ff];
		pre[ff]=cnt;
	}
	void solve(int s)
	{
		memset(dis,0,sizeof(dis));
		h=1;t=1;
		q[1]=s;dis[s]=0;
		int i,j,x,y;
		while(h<=t)
		{
			x=q[h];h++;
			for(i=pre[x];i;i=nxt[i])
			{
				j=to[i];
				dis[j]=max(dis[j],dis[x]+1);
				du[j]--;
				if(!du[j]) t++,q[t]=j;
			}
		}
	}
}z,f;
struct heap
{
	priority_queue<int>p,q;
	void del(int x)
	{
		q.push(x);
		while(!p.empty()&&!q.empty()&&q.top()==p.top())
		{
			q.pop();
			p.pop();
		}
	}
	void add(int x)
	{
		p.push(x);
	}
	int max()
	{
		return p.top();
	}
};
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	int i,j,x,y;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		du[y]++;z.du[y]++;f.du[x]++;
		z.ae(x,y);f.ae(y,x);
	}
	for(i=1;i<=n;i++)
	{
		du[i]++;du[n+1]++;
		z.du[i]++;z.du[n+1]++;
		f.du[i]++;f.du[0]++;
		z.ae(0,i);f.ae(i,0);
		z.ae(i,n+1);f.ae(n+1,i);
	}
	z.solve(0);
	f.solve(n+1);
	for(i=1;i<=m+2*n;i++)
	w[i]=z.dis[z.fr[i]]+f.dis[z.to[i]]-1;
	heap p;
	h=t=1;q[1]=0;
	int minn=707185547,ans;
	while(h<=t)
	{
		x=q[h];h++;
		for(i=f.pre[x];i;i=f.nxt[i])
		p.del(w[i]);
		if(x!=0&&x!=n+1)
		{
			y=p.max();
			if(y<minn) minn=y,ans=x;
		}
		for(i=z.pre[x];i;i=z.nxt[i])
		{
			j=z.to[i];
			p.add(w[i]);
			du[j]--;
			if(!du[j]) t++,q[t]=j;
		}
	}
	printf("%d %d",ans,minn);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值