题目链接:点击打开链接
题意:
给定n个人 m个逃生洞穴 至少k个人进入逃生洞穴 挖洞时间c
下面n*m的矩阵表示每个人到每个洞需要的时间。
一个洞穴开始只能容纳一个人,可以被拓展一次,即变成可以容纳2个人(一个洞穴只能被拓展一次)
当a进入洞穴后不会开始拓展,直到有一个人b在洞穴门口等,a才会开始拓展空间,拓展的时间的c,c时间后b才能进入洞穴。
问至少k个人进入洞穴的最短时间。(数据保证有解)
1、
我们可以认为一个洞穴里面有2个房间,而第二个房间就相当于某个人跑到了这个洞穴然后这个人自己开始挖,则花费就是到达洞穴的时间+c
跑个费用流,看最后流量是否>=k
把洞穴拆成2个点,人和源点建边,洞穴的2个拆点都和汇点建边。
每个人和洞穴的2个点建边,流量是1,费用是距离,这样对于每个人一定是先跑进第一个房间,因为费用小,则当这个人跑到第二个房间时说明第一个房间一定有人了。
2、
其实可以直接用二分的答案来判断边是否有效,所以网络流即可。
费用流会t T^T
#include <stdio.h> #include <string.h> #include <iostream> #include <math.h> #include <queue> #include <set> #include <algorithm> using namespace std; inline void rd(int &n){ n = 0; char c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c >= '0' && c <= '9') n *= 10, n += (c - '0'),c = getchar(); } #define ll int #define N 5000 #define M 30500 #define inf 107374182 #define inf64 1152921504606846976 struct Edge{ ll from, to, cap, nex, cost; }edge[M*2];//注意这个一定要够大 不然会re 还有反向弧 ll head[N], edgenum; void add(ll u, ll v, ll cap, ll cost){ //如果是有向边则:add(u,v,cap); 如果是无向边则:add(u,v,cap,cap); Edge E = { u, v, cap, head[u], cost}; edge[ edgenum ] = E; head[u] = edgenum ++; Edge E2= { v, u, 0, head[v], 0}; edge[ edgenum ] = E2; head[v] = edgenum ++; } ll sign[N]; bool BFS(ll from, ll to){ memset(sign, -1, sizeof(sign)); sign[from] = 0; queue<ll>q; q.push(from); while( !q.empty() ){ ll u = q.front(); q.pop(); for(ll i = head[u]; i!=-1; i = edge[i].nex) { ll v = edge[i].to; if(sign[v]==-1 && edge[i].cap) { sign[v] = sign[u] + 1, q.push(v); if(sign[to] != -1)return true; } } } return false; } ll Stack[N], top, cur[N]; ll Dinic(ll from, ll to){ ll ans = 0; while( BFS(from, to) ) { memcpy(cur, head, sizeof(head)); ll u = from; top = 0; while(1) { if(u == to) { ll flow = inf, loc;//loc 表示 Stack 中 cap 最小的边 for(ll i = 0; i < top; i++) if(flow > edge[ Stack[i] ].cap) { flow = edge[Stack[i]].cap; loc = i; } for(ll i = 0; i < top; i++) { edge[ Stack[i] ].cap -= flow; edge[Stack[i]^1].cap += flow; } ans += flow; top = loc; u = edge[Stack[top]].from; } for(ll i = cur[u]; i!=-1; cur[u] = i = edge[i].nex)//cur[u] 表示u所在能增广的边的下标 if(edge[i].cap && (sign[u] + 1 == sign[ edge[i].to ]))break; if(cur[u] != -1) { Stack[top++] = cur[u]; u = edge[ cur[u] ].to; } else { if( top == 0 )break; sign[u] = -1; u = edge[ Stack[--top] ].from; } } } return ans; } void init(){memset(head,-1,sizeof head);edgenum = 0;} int n, m, k, c, from, to; bool work(int x){ for(int i = 0; i < edgenum; i+=2){ edge[i].cap = (edge[i].cost <= x); edge[i^1].cap = 0; } return Dinic(from, to) >= k; } void input(){ rd(n); rd(m); rd(k); rd(c); from = 0, to = n+2*m+1; init(); for(int i = 1; i <= n; i++) { add(from, i, 1, 0); for(int j = 1; j <= m; j++) { int cost; rd(cost); add(i, n+j, 1, cost); add(i, n+m+j, 1, cost+c); } } for(int i = 1; i <= m; i++) { add(n+i, to, 1, 0); add(n+m+i, to, 1, 0); } } int main(){ int T;scanf("%d",&T); while(T--){ input(); int l = 0, r = 20000010, ans = r; while(l <= r){ int mid = (l+r)>>1; if(work(mid)) r = mid-1, ans = min(ans, mid); else l = mid+1; } printf("%d\n", ans); } return 0; }