题目大意:给你一个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);
}