zoukankan      html  css  js  c++  java
  • 洛谷P3652 csh和zzy的战争 题解

    简述题意

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

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

    对于中转岛 (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");
    }
    
  • 相关阅读:
    哈希表-环形链表
    双链表
    文本框值是否为空,有就隐藏提示语,反之显示
    常用正则
    jquery遍历赋值
    动态更改地址栏参数
    截取地址栏参数
    java.lang.NoSuchMethodException
    文字超出范围隐藏,改变隐藏“...”颜色
    网页设定定时自动跳转
  • 原文地址:https://www.cnblogs.com/nao-nao/p/14798782.html
Copyright © 2011-2022 走看看