CF852D 【Exploration plan】
题目大意: 给定一个 v 个点 e 条边的带权无向图,在图上有 n 个人,第 i 个人位于点 xi,一个人通过一条边需要花费这条边的边权的时间。现在每个人可以自由地走。求最短多少时间后满足结束后有人的节点数 ≥ k
虽然我wa了几个点,但问题不大。
我们可以二分时间,然后建图跑网络流。
具体如下:
1.先floyd预处理一遍。
2.二分时间,对于每个有人的点,向时间内能到的点连边,流量为1
3.超级源点向有人的点连边流量为1,所有点向超级汇点连边,流量为1
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=1e9;
const int N=2e5+5;
struct node
{
int next,to,w;
}a[N];
int v,e,n,s,t,k;
int head[N],cnt,b[N],c[N],w[N],x[N],dis[666][666];
int cur[N],d[N];
queue <int> q;
void add(int x,int y,int dis)
{
a[++cnt].next=head[x];
a[cnt].to=y;
a[cnt].w=dis;
head[x]=cnt;
}
bool bfs(int s,int t)
{
memset(d,0x7f,sizeof(d));
while(!q.empty()) q.pop();
for(int i=s;i<=t;i++) cur[i]=head[i];
d[s]=0;
q.push(s);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=a[i].next)
{
int v=a[i].to;
if(d[v]>inf&&a[i].w)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
if(d[t]<inf) return true;
else return false;
}
int dfs(int now,int t,int limit)
{
if(!limit||now==t) return limit;
int flow=0,f;
for(int i=cur[now];i;i=a[i].next)
{
cur[now]=i;
int v=a[i].to;
if(d[v]==d[now]+1&&(f=dfs(v,t,min(limit,a[i].w))))
{
flow+=f;
limit-=f;
a[i].w-=f;
a[i^1].w+=f;
if(!limit) break;
}
}
return flow;
}
bool check(int mid)
{
//重新建图
cnt=1;
memset(head,0,sizeof(head));
memset(cur,0,sizeof(cur));
memset(d,0,sizeof(d));
//对于每个有人的点,向时间内能到的点连边,流量为1
for(int i=1;i<=n;i++)
for(int j=1;j<=v;j++)
if(dis[x[i]][j]<=mid) add(x[i],j,1),add(j,x[i],0);
s=0,t=v+1;
for(int i=1;i<=n;i++)
add(0,x[i],1),add(x[i],0,0);//超级源点向有人的点连边流量为1
for(int i=1;i<=v;i++)
add(i,t,1),add(t,i,0);//所有点向超级汇点连边,流量为1
int ans=0;
while(bfs(s,t)) ans+=dfs(s,t,inf); //网络流
if(ans>=k) return 1;
else return 0;
}
int main()
{
scanf("%d%d%d%d",&v,&e,&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&x[i]);
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
dis[i][j]=1e9;
for(int i=1;i<=e;i++)
{
scanf("%d%d%d",&b[i],&c[i],&w[i]);
dis[b[i]][c[i]]=dis[c[i]][b[i]]=min(dis[b[i]][c[i]],w[i]);
}
//floyd预处理最短路径
for(int k=1;k<=v;k++)
for(int i=1;i<=v;i++)
for(int j=1;j<=v;j++)
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
//二分时间
int l=0,r=1000000;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}