UVA12125 题解
UVA12125 题解
前言
一道经典的最大流问题,考察拆点和建图技巧
思路
对于每一个冰块上企鹅的数量,我们可以从源点 \(S\) 向每一个冰块连一条容量为该冰块上企鹅数量的边。
对于每一个冰块上所限制的起跳数量,直接建图不好建,考虑拆点,对于第 \(i\) 个冰块,我们将它拆成 \(i\) 和 \(n + i\) 两个冰块,此时从 \(i\) 节点向 \(i+1\) 节点连一条容量为对应限制的起跳数量的边,这样可以保证每一个冰块实际起跳次数总是不大于最大起跳次数。
对于每一对冰块,我们判断企鹅的最长跳跃距离和这一对冰块的距离,如果前者大于后者,则这对冰块应该连边,容量是几呢?因为我们已经保证每个冰块上所限制的起跳数量了,所以这里容量直接设成 \(INF\) 即可。
因为不知道最终企鹅会聚集在哪一个点 \(T\) ,那么我们枚举 \(T\) 的位置,对于每一个位置求一遍最大流,如果合法(标准见下一段),那么记录答案。
对于每个最大流,只需判断最大流和企鹅的数量,如果相等,那么说明所有企鹅都到了 \(T\) 点,合法;否则,有一些企鹅没有到达 \(T\) 点,不合法。
注意
- 每一行不可以有多余的行末空格。
- 遍历 \(T\) 时,需要初始化残留网络。
- 对于复杂度,如果仔细算一算的话时间还是挺紧的,不过网络流的复杂度就是
瞎扯,很难达到理论最大值。 - 注意点数和边数上界,点数好算 \(100\times 2 + 2\),边数需要仔细考虑,计算表达式为:\((100\times 100 + 100 \times 2)\times 2\),解释一下,最多有 \(100\) 个点,那么最多的冰块组合有 \(100\times 100\) 个,对于源点汇点最多连 \(100\) 条边,由于维护的是残留网络,正向反向边都需要,所以最后要乘 \(2\)。
Code
#include <iostream>
#include <cstring>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 210, M = 20410, INF = 1e8;
const double eps = 1e-8;
int n, S, T;
double D;
int h[N], e[M], f[M], ne[M], idx;
int q[N], depth[N], cur[N];
PII p[N];
bool check(PII a, PII b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return dx * dx + dy * dy < D * D + eps * eps; //精度问题
}
void add(int a, int b, int c)
{
e[idx] = b;
f[idx] = c;
ne[idx] = h[a];
h[a] = idx ++ ;
e[idx] = a;
f[idx] = 0;
ne[idx] = h[b];
h[b] = idx ++ ;
return;
}
bool bfs()
{
int hh = 0, tt = 0;
memset(depth, -1, sizeof depth);
q[0] = S;
depth[S] = 0;
cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (depth[ver] == -1 && f[i] > 0)
{
depth[ver] = depth[t] + 1;
cur[ver] = h[ver];
q[ ++ tt] = ver;
if (ver == T) return true;
}
}
}
return false;
}
int dfs(int u, int lmt)
{
if (u == T) return lmt;
int flow = 0;
for (int i = cur[u]; ~i && flow < lmt; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (depth[ver] == depth[u] + 1 && f[i] > 0)
{
int t = dfs(ver, min(f[i], lmt - flow));
if (!t) depth[ver] = -1;
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic()
{
int res = 0, flow = 0;
while (bfs())
while (flow = dfs(S, INF))
res += flow;
return res;
} //一直到这里都是板子
int main()
{
int cases;
cin >> cases;
while (cases -- )
{
memset(h, -1, sizeof h); //记着初始化h
idx = 0;
cin >> n >> D;
S = 0;
int tot = 0;
for (int i = 1; i <= n; i ++ )
{
int x, y, a, b;
cin >> x >> y >> a >> b;
p[i] = {x, y};
add(S, i, a);
add(i, n + i, b);
tot += a;
}
for (int i = 1; i <= n; i ++ )
for (int j = i + 1; j <= n; j ++ )
if (check(p[i], p[j]))
{
add(n + i, j, INF);
add(n + j, i, INF);
}
//对应思路,代码实现
bool flag = 0;
int cnt = 0;
for (int i = 1; i <= n; i ++ )
{
T = i;
for (int j = 0; j < idx; j += 2) //枚举正向边
{
f[j] += f[j ^ 1]; //将正向边弄成满流
f[j ^ 1] = 0; //反向边弄成0,初始化
}
if (dinic() == tot)
{
if (cnt) cout << " ";
cout << i - 1;
cnt ++ ;
flag = 1;
}
}
if(!cnt) puts("-1");
else puts("");
}
return 0;
}