洛谷P3652 csh和zzy的战争 题解

简述题意

给定一些货源地,一些中转岛,以及一个终点——军事基地。

货源地分为两种,普通货和特殊货。

对于中转岛 \(i\) ,最多可以中转 \(w_i\) 件货物,中转岛发向另一个中转岛或军事基地的货物分别不能超过 \(d_i\)

上面货物皆指普通货物,特殊货物不会受到限制。

中转岛之间有 \(e\) 条航道,航道 \(i\) 有边权。开辟航线 \(u-v\) 的代价是 \(u\)\(v\) 之间的最短路。

最后全局的代价是航线代价的最大值。

要求在所有货物都能够到达军事基地的前提下,最小代价是多少。

解题思路

看到代价是最大值,要求其最小,考虑可以二分。将代价小于等于二分答案 \(\text{mid}\) 的航线全都加进去,看是否能将所有货物都送达军事基地。二分正确性显然。

分别考虑特殊货物和普通货物。

首先考虑特殊货物,只要货源地和军事基地相连,即可全部运往军事基地。我们将所有航线全部连上,并连反边(基地连向中转岛,中转岛连向货源地)。中转岛间边权设为航线开辟代价。从军事基地开始使用最短路算法,最小化到各个货源地的路径上的最大边权。可以发现将所有特殊货源地的答案取 \(\text{max}\) 是二分下界。

至于如何求出两岛间航线代价,使用 \(n^3\) 的最短路即可。

然后考虑如何求是否所有普通货物都可到达军事基地。

使用网络流求最大流。货源地连中转岛容量为 \(\text{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\n",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\n",mid); 
	} 
	if(l != inf) printf("%lld",l);
	else printf("-1");
}
posted @ 2021-05-22 14:30  nao-nao  阅读(61)  评论(0编辑  收藏  举报