hdu-6749 Mosquito (二分,最大流,优化建图)
2020 年百度之星·程序设计大赛 - 初赛一0 Mosquito
Problem Description
房间是个 n∗m 的网格,一共有 k 个窗户,都在上下左右四条边上。在第 0 时刻,每个窗户对应的格子上都会出现若干只蚊子。
蚊子每个时刻可以往上下左右移动一格或者呆在原地不动。
假设这些蚊子都足够聪明,请问最少花费多少时刻,使得所有格子上都有至少一只蚊子?
蚊子在第 0 时刻不能动。
思路:
当\(\sum_{i=1}^k z_i < n*m\)答案为-1,其他均有答案。
设花费\(t_0\)时刻可以满足条件,那么\(t_0+1\)一定也满足,则具有单调性,所以答案的时间\(\mathit t\) 可以二分求出。
问题转化为检验型问题,即给定一个时间\(\mathit t\),问能否满足所有格子都有蚊子。
该问题可以通过建流量网络求最大流来解决。
建图方法:
设源点为\(\mathit S\),汇点\(\mathit T\)。
1、源点\(\mathit S\)对每一个窗户\(W_i\),建立流量为\(z_i\)的有向边。
2、每一个窗口\(W_i\)对距离其位置曼哈顿距离小于\(\mathit t\) 的格点建立流量为\(\text 1\)的有向边。
3、每一个格点对汇点\(\mathit T\)建立流量为\(\text 1\)的边。
令\(Flow\)为从源点到汇点的最大流,那么如果\(n*m\le Flow\)则满足条件。
我们知道这样建立的网络流的节点个数是\(n*m+k+2\),用任何高效的最大流算法都是无法在时限内求出的。
那么我们来思考如何优化建图,
我们仔细思考(画图更容易理解),这\(n*m\)个格点,有很多节点的入边,出边,以及它们的流量都是完全相等的。
我们知道,在流量网络中,把这种非汇源点且入边和出边完全相等的节点们(假设\(num\)个)浓缩成一个节点,节点的每一个边的流量值扩大\(num\)倍,对源点到汇点的最大流是没有影响的。
那么我们的网络中,每一个格子节点的出边都是一样的,那么的区别只在于入边,
但\(k\leq 6\),我们可以用二进制状态压缩代表一个节点的入边有哪些窗户,然后状态相等的节点就可以缩成一个节点。
细节见代码:
code:
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
ll lcm(ll a, ll b) {return a / gcd(a, b) * b;}
ll powmod(ll a, ll b, ll MOD) { if (a == 0ll) {return 0ll;} a %= MOD; ll ans = 1; while (b) {if (b & 1) {ans = ans * a % MOD;} a = a * a % MOD; b >>= 1;} return ans;}
ll poww(ll a, ll b) { if (a == 0ll) {return 0ll;} ll ans = 1; while (b) {if (b & 1) {ans = ans * a ;} a = a * a ; b >>= 1;} return ans;}
void Pv(const vector<int> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%d", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
void Pvl(const vector<ll> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%lld", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
inline long long readll() {long long tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
inline int readint() {int tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
void pvarr_int(int *arr, int n, int strat = 1) {if (strat == 0) {n--;} repd(i, strat, n) {printf("%d%c", arr[i], i == n ? '\n' : ' ');}}
void pvarr_LL(ll *arr, int n, int strat = 1) {if (strat == 0) {n--;} repd(i, strat, n) {printf("%lld%c", arr[i], i == n ? '\n' : ' ');}}
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
/*** TEMPLATE CODE * * STARTS HERE ***/
struct Edge
{
int from, to;
ll cap, flow; // cap 并不减少,只是flow在增,有残余就是cap>flow
};
int si = 0;
struct ISAP
{
int n, m, s, t;
vector<Edge>edges;
vector<int>G[N];
bool vis[N];
int d[N], cur[N];
int p[N], num[N]; //比Dinic算法多了这两个数组,p数组标记父亲结点,num数组标记距离d[i]存在几个
void addedge(int from, int to, ll cap)
{
edges.push_back((Edge) {from, to, cap, 0});
edges.push_back((Edge) {to, from, 0, 0});
int m = edges.size();
G[from].push_back(m - 2);
G[to].push_back(m - 1);
}
void init()
{
MS0(d);
edges.clear();
for (int i = 0; i <= n; ++i) {
G[i].clear();
}
}
ll Augumemt()
{
ll x = t, a = INF;
while (x != s) //找最小的残量值
{
Edge&e = edges[p[x]];
a = min(a, e.cap - e.flow);
x = edges[p[x]].from;
}
x = t;
while (x != s) //增广
{
edges[p[x]].flow += a;
edges[p[x] ^ 1].flow -= a;
x = edges[p[x]].from;
}
return a;
}
void bfs()//逆向进行bfs
{
memset(vis, 0, sizeof(vis));
queue<int>q;
q.push(t);
d[t] = 0;
vis[t] = 1;
while (!q.empty())
{
int x = q.front(); q.pop();
int len = G[x].size();
for (int i = 0; i < len; i++)
{
Edge&e = edges[G[x][i]];
if (!vis[e.from] && e.cap > e.flow)
{
vis[e.from] = 1;
d[e.from] = d[x] + 1;
q.push(e.from);
}
}
}
}
ll Maxflow(int s, int t) //根据情况前进或者后退,走到汇点时增广
{
this->s = s;
this->t = t;
ll flow = 0;
bfs();
memset(num, 0, sizeof(num));
for (int i = 0; i < n; i++)
num[d[i]]++;
int x = s;
memset(cur, 0, sizeof(cur));
while (d[s] < n)
{
if (x == t) //走到了汇点,进行增广
{
flow += Augumemt();
x = s; //增广后回到源点
}
int ok = 0;
for (int i = cur[x]; i < G[x].size(); i++)
{
Edge&e = edges[G[x][i]];
if (e.cap > e.flow && d[x] == d[e.to] + 1)
{
ok = 1;
p[e.to] = G[x][i]; //记录来的时候走的边,即父边
cur[x] = i;
x = e.to; //前进
break;
}
}
if (!ok) //走不动了,撤退
{
int m = n - 1; //如果没有弧,那么m+1就是n,即d[i]=n
for (int i = 0; i < G[x].size(); i++)
{
Edge&e = edges[G[x][i]];
if (e.cap > e.flow)
m = min(m, d[e.to]);
}
if (--num[d[x]] == 0)break; //如果走不动了,且这个距离值原来只有一个,那么s-t不连通,这就是所谓的“gap优化”
num[d[x] = m + 1]++;
cur[x] = 0;
if (x != s)
x = edges[p[x]].from; //退一步,沿着父边返回
}
}
return flow;
}
// 调用前给这里的n赋值,一般为n=T+1;
} gao;
/*
int S = 0;
int T = n + 10;
gao.n = T + 1;
gao.addedge(S, i, c[i]);
gao.Maxflow(S, T);
gao.init();
*/
int n, m;
int k;
int info[maxn][maxn];
int x[maxn];
int y[maxn];
int w[maxn];
int cnt[maxn];
bool check(int t)
{
repd(i, 1, n)
{
repd(j, 1, m)
{
info[i][j] = 0;
}
}
repd(q, 0, k - 1)
{
repd(i, max(1, x[q] - t), min(n, x[q] + t))
{
repd(j, max(1, y[q] - t), min(m, y[q] + t))
{
if (abs(i - x[q]) + abs(j - y[q]) <= t)
{
info[i][j] |= (1 << q);
}
}
}
}
int maxstate = (1 << k) - 1;
repd(i, 0, maxstate)
{
cnt[i] = 0;
}
repd(i, 1, n)
{
repd(j, 1, m)
{
cnt[info[i][j]]++;
}
}
if (cnt[0] > 0)
return 0; // 小小的优化
int T = maxstate + 10;
int S = 0;
gao.n = T + 1;
/*
gao.addedge(S, i, c[i]);
gao.Maxflow(S, T);
*/
repd(j, 0, k - 1)
{
gao.addedge(S, maxstate + j + 1, w[j]);
}
repd(i, 1, maxstate)
{
for (int j = 0; j < k; ++j)
{
if (i & (1 << j))
{
gao.addedge(maxstate + j + 1, i, w[j]);
}
}
gao.addedge(i, T, cnt[i]);
}
int res = gao.Maxflow(S, T);
gao.init();
return res >= n * m;
}
int main()
{
freopen("C:\\code\\input.txt", "r", stdin);
//freopen("C:\\code\\output.txt","w",stdout);
int t;
t = readint();
while (t--)
{
n = readint(); m = readint();
k = readint();
repd(i, 1, n)
{
repd(j, 1, m)
{
info[i][j] = 0;
}
}
int sum = 0;
repd(i, 0, k - 1)
{
x[i] = readint();
y[i] = readint();
w[i] = readint();
sum += w[i];
}
if (n * m > sum)
{
printf("-1\n");
continue;
}
int l = 0;
int r = m + n - 2;
int mid;
int ans;
while (l <= r)
{
mid = (l + r) >> 1;
if (check(mid))
{
ans = mid;
r = mid - 1;
} else
{
l = mid + 1;
}
}
printf("%d\n", ans );
}
return 0;
}