有一条东西向流淌的河,宽为 W,河中有 N 块石头,每块石头的坐标(Xi, Yi)和最大承受人数 Ci 已知。现在有 M 个游客在河的南岸,他们想穿越这条河流,但是每个人每次最远只能跳 D 米,每跳一次耗时 1 秒。问他们能否全部穿越这条河流,如果能,最少需要多长时间。 <= N <= 50, 0 < M <= 50, 0 <= D <= 1000, 0 < W(0<= 1000, 0 < Xi < 1000, 0 < Yi < W, 0 <= Ci <= 1000)。刚看完这题,想当然的认为它是一道最小费用流问题。但是当WA之后我才明白,这题并不是去求一个给定网络的最大流,而是计算这个网络随着时间推移每次能够留出多少流量。我们通过枚举时间的方式来决定在什么时刻能够把所有的人全部送到对岸。注意人是可以从河这岸的任意x坐标出发的。
对于第一组数据,我们可以给出这样的随着时间的整个网络流量表:
t = 1s, flow = 0; t = 2s, flow = 0;
t = 3s, flow = 3; t = 4s, flow = 3;
t = 5s, flow = 3; t = 6s, flow = 3;
将每个时刻的流量相加,得到在t为6s的时候能够满足将10人运送到河的对岸。所以我们要建立一个分层的网络(time-expanded network)随着时间的增加每次能够在网络中添加新的边,于是我们可以得到新的网络流量。因为流量在点上,于是我们要进行拆点。又因为枚举了时间,所以我们还要对时间进行拆点。
代码如下:
#include <cstring> #include <cstdlib> #include <cstdio> #include <algorithm> #include <queue> #include <cmath> #define INF 0x3fffffff #define T(x, i) ((x)+(i-1)*50) #define RE(x) ((x)^1) #define CP(x) ((x)+5000) using namespace std; // 每个点要被拆成至多100个点,分别表示每个点在此时刻的状态 int N, M, D, W, head[10005], dis[10005], idx, G[55][55]; const int source = 10003, sink = 10004; struct point { int x, y, cap; }p[55]; struct Edge { int v, cap, next; }e[1210000]; inline void init() { idx = -1; memset(head, 0xff, sizeof (head)); memset(G, 0x3f, sizeof (G)); } inline double dist(int x1, int y1, int x2, int y2) { return sqrt(double((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2))); } inline bool judge(int a, int b) { double x = D, y = dist(p[a].x, p[a].y, p[b].x, p[b].y); if (x - y > 1e-6 || fabs(x - y) <= 1e-6) { return true; } return false; } void insert(int f, int t, int c) { ++idx; e[idx].v = t, e[idx].cap = c; e[idx].next = head[f], head[f] = idx; } bool spfa() { int u; queue<int>q; memset(dis, 0xff, sizeof (dis)); dis[source] = 0; q.push(source); while (!q.empty()) { u = q.front(); q.pop(); for (int i = head[u]; i != -1; i = e[i].next) { if (dis[e[i].v] == -1 && e[i].cap > 0) { dis[e[i].v] = dis[u]+1; q.push(e[i].v); } } } return dis[sink] != -1; } int dfs(int u, int flow) { if (u == sink) { return flow; } int tf = 0, sf; for (int i = head[u]; i != -1; i = e[i].next) { if (dis[u]+1 == dis[e[i].v] && e[i].cap > 0 && (sf = dfs(e[i].v, min(flow-tf, e[i].cap)))) { e[i].cap -= sf, e[RE(i)].cap += sf; tf += sf; if (tf == flow) { return flow; } } } if (!tf) { dis[u] = -1; } return tf; } void Dinic(int &ans) { while (spfa()) { ans += dfs(source, INF); } } int main() { int ans = 0, flag = 0; init(); scanf("%d %d %d %d", &N, &M, &D, &W); p[0].x = p[0].y = p[0].cap = 0; // 将源点与他们的距离也计算出来 for (int i = 1; i <= N; ++i) { scanf("%d %d %d", &p[i].x, &p[i].y, &p[i].cap); } if (D >= W) { // 如果能够跳跃的距离大于河的宽度,那么直接退出 printf("1\n"); return 0; } for (int i = 1; i <= N; ++i) { for (int j = 1; j <= N; ++j) { if (judge(i, j) && i != j) { G[i][j] = 1; } } } for (int i = 1; i <= M+N; ++i) { // 枚举能够跳过去的时间 for (int j = 1; j <= N; ++j) { // 枚举第i个节点,源点不许要按照时间来进行拆点 if (p[j].y <= D) { // 能够从源点走到的边,在任意时刻都是可以走的 insert(source, T(j, i), INF); insert(T(j, i), source, 0); } if (p[j].y + D >= W) { insert( CP( T(j, i) ), sink, INF ); insert( sink, CP( T(j, i) ), 0 ); } insert(T(j, i), CP( T(j, i) ), p[j].cap); // 拆点限制流量 insert(CP( T(j, i) ), T(j, i), p[j].cap); for (int k = 1; k <= N; ++k) { if (G[j][k] == 1 && k != j) { // 如果他们之间的距离为1的话 insert( CP( T(j, i) ), T(k, i+1), INF ); insert( T(k, i+1), CP( T(j, i) ), 0 ); } } } Dinic(ans); if (ans >= M) { printf("%d\n", i + 1); flag = 1; break; } } if (!flag) { printf("IMPOSSIBLE\n"); } return 0; }