简述题意
给定一些货源地,一些中转岛,以及一个终点——军事基地。
货源地分为两种,普通货和特殊货。
对于中转岛 (i) ,最多可以中转 (w_i) 件货物,中转岛发向另一个中转岛或军事基地的货物分别不能超过 (d_i) 。
上面货物皆指普通货物,特殊货物不会受到限制。
中转岛之间有 (e) 条航道,航道 (i) 有边权。开辟航线 (u-v) 的代价是 (u) 和 (v) 之间的最短路。
最后全局的代价是航线代价的最大值。
要求在所有货物都能够到达军事基地的前提下,最小代价是多少。
解题思路
看到代价是最大值,要求其最小,考虑可以二分。将代价小于等于二分答案 ( ext{mid}) 的航线全都加进去,看是否能将所有货物都送达军事基地。二分正确性显然。
分别考虑特殊货物和普通货物。
首先考虑特殊货物,只要货源地和军事基地相连,即可全部运往军事基地。我们将所有航线全部连上,并连反边(基地连向中转岛,中转岛连向货源地)。中转岛间边权设为航线开辟代价。从军事基地开始使用最短路算法,最小化到各个货源地的路径上的最大边权。可以发现将所有特殊货源地的答案取 ( ext{max}) 是二分下界。
至于如何求出两岛间航线代价,使用 (n^3) 的最短路即可。
然后考虑如何求是否所有普通货物都可到达军事基地。
使用网络流求最大流。货源地连中转岛容量为 ( ext{inf}) ,中转岛之间和中转岛到军事基地的边容量为 (d) ,将中转岛拆点控制经过岛的流量不超过 (w) 。
随后跑最大流就好了,此题解决。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define int long long
#define Mp make_pair
#define val lim
#define pb push_back
using namespace std;
int read()
{
int a = 0,x = 1;char ch = getchar();
while(ch > '9' || ch < '0') {if(ch == '-') x = -1;ch = getchar();}
while(ch >= '0' && ch <= '9') {a = a*10 + ch-'0';ch = getchar();}
return a*x;
}
const int N=1e6+7,inf = 1e9+7;
int n,m,X,E,a[N],w[N],d[N],b[N],M,s,t;
vector<int>g[307];
int mp[500][500],dis[N],vis[N];
int head[N],go[N],nxt[N],cnt,lim[N],cur[N];
void add(int u,int v,int w)
{
go[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
lim[cnt] = w;
}
struct node{int u,v,w;}edge[N];
void dij(int pos)
{
priority_queue<pair<int,int> >q;
q.push(Mp(0,pos));
while(!q.empty()) {
int u = q.top().second;
if(vis[u]) {q.pop();continue;}
dis[u] = -q.top().first;q.pop();vis[u] = 1;
for(int e = head[u];e;e = nxt[e]) {
int v = go[e];if(dis[v]) continue;
q.push(Mp(min(-dis[u],-lim[e]),v));
}
}
}
bool BFS()
{
for(int i = s;i <= t;i ++) dis[i] = 0;
queue<int>q;q.push(s);dis[s] = 1;
while(!q.empty()) {
int u = q.front();q.pop();
for(int e = head[u];e;e = nxt[e]) {
int v = go[e];
if(dis[v] || !lim[e]) continue;
dis[v] = dis[u] + 1;q.push(v);
}
}
return dis[t];
}
int DFS(int u,int limit)
{
if(u == t || !limit) return limit;
int ret = 0;
for(int &e = head[u];e; e= nxt[e]) {
int v = go[e];if(dis[v] != dis[u] + 1|| !lim[e]) continue;
int tmp = DFS(v,min(lim[e],limit));
lim[e] -= tmp,lim[e^1] += tmp;
limit -= tmp,ret += tmp;
if(!limit) break;
}
return ret;
}
bool check(int limit)
{
for(int i = s;i <= t;i ++) head[i] = 0;cnt = 1;int sum = 0;
for(int i = 1;i <= n;i ++) add(s,i,a[i]),add(i,s,0),sum += a[i];
//货源地和中转岛
for(int i = 1;i <= m;i ++) {
if(b[i]) add(i+n+m,t,d[i]),add(t,i+n+m,0);
//中转岛和军事基地
add(i+n,i+n+m,w[i]);add(i+n+m,i+n,0);
//拆点部分
for(auto it = g[i].begin();it != g[i].end();++ it) {
if(*it <= n) add(*it,i+n,inf),add(i+n,*it,0);
//只连普通货源地,特殊货源地不必理会
}
}
for(int i = 1;i <= M && edge[i].w <= limit;i ++) {
add(edge[i].u+n+m,edge[i].v+n,d[edge[i].u]);add(edge[i].v+n,edge[i].u+n+m,0);
add(edge[i].v+n+m,edge[i].u+n,d[edge[i].v]);add(edge[i].u+n,edge[i].v+n+m,0);
//连上可以连的边
}
// puts("!");printf("sum=%d
",sum);
for(int i = s;i <= t;i ++) cur[i] = head[i];
while(BFS()) {sum -= DFS(s,inf);for(int i = s;i <= t;i ++) head[i] = cur[i];}
return !sum;//sum=0说明所有货物都可以送到军事基地
}
bool cmp(node a,node b) {return a.w < b.w;}
signed main()
{
// freopen("in.in","r",stdin);
n = read(),m = read(),X = read(),E = read();
for(int i = 1;i <= n+X;i ++) a[i] = read();
for(int i = 1;i <= m;i ++) w[i] = read();
for(int i = 1;i <= m;i ++) d[i] = read();
for(int i = 1;i <= m;i ++) {
int k = read();
for(int j = 1;j <= k;j ++) {
int u = read();g[i].pb(u);
add(n+X+i,u,0);
}
b[i] = read();if(b[i]) add(n+X+m+1,n+X+i,0);
}
for(int i = 1;i <= E;i ++) {
int u = read(),v = read(),w = read();
mp[u][v] += w,mp[v][u] += w;//这里是看讨论区提到的,我就这么写了
}
for(int i = 1;i <= m;i ++) for(int j = 1;j <= m;j ++) if(!mp[i][j]) mp[i][j] = inf; else if(i!=j) add(n+X+i,n+X+j,mp[i][j]);
//边先连上,为处理特殊货物做准备,显然不需要连那些本来没有直接连边的航线。连接他们不会使答案更优。
for(int k = 1;k <= m;k ++) for(int i = 1;i <= m;i ++) for(int j = 1;j <= m;j ++) mp[i][j] = min(mp[i][j],mp[i][k]+mp[k][j]);
dij(n+m+X+1);int l = 0,r,mid;//跑最短路
for(int i = n+1;i <= n+X;i ++) l = max(l,dis[i]);//赋值下界
memset(head,0,sizeof(head)); cnt = 1;s = 0,t = n+2*m+1;
for(int i = 1;i <= m;i ++) for(int j = i+1;j <= m;j ++) if(mp[i][j] < inf) edge[++M] = (node){i,j,mp[i][j]};
//把所有航线拿出来排序
sort(edge+1,edge+1+M,cmp);r = inf;
while(l<r) {//二分答案
mid = l+r>>1;
if(check(mid)) r = mid;
else l = mid + 1;
// printf("mid=%d
",mid);
}
if(l != inf) printf("%lld",l);
else printf("-1");
}