网络流(四) 最大流判断,分层图,拆点
经典最大值最小问题,可以直接二分出最大的边权mid,看是否存在一个最大流,其中所有边权都小于等于mid,且流量大于K,不断二分下去即可找到答案。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define int long long
using namespace std;
const int N = 210, M = 80010, inf = 1e18;
int n, m, S, T, K;
int h[N], e[M], f[M], ne[M], w[M], idx;
int q[N], d[N], cur[N];
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
e[idx] = a, w[idx] = c, ne[idx] = h[b], h[b] = idx ++;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S;
d[S] = 0;
cur[S] = h[S];
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int ver = e[i];
if(d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if(ver == T){
return true;
}
q[++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if(u == T){
return limit;
}
int flow = 0;
for(int i = cur[u] ; i != -1 && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if(d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(f[i], limit - flow));
if(!t){
d[ver] = -1;
}
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while(bfs()){
while(flow = find(S, inf)){
r += flow;
}
}
return r;
}
bool check(int mid){
for(int i = 0; i < idx; i ++){
if(w[i] > mid){
f[i] = 0;
}
else{
f[i] = 1;
}
}
return dinic() >= K;
}
signed main(){
scanf("%lld%lld%lld",&n, &m, &K);
S = 1;
T = n;
memset(h, -1, sizeof(h));
while(m --){
int a, b, c;
scanf("%lld%lld%lld",&a, &b, &c);
add(a, b, c);
}
int l = 0, r = 1e9;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)){
r = mid;
}
else{
l = mid + 1;
}
}
printf("%lld\n", r);
}
当网络流问题中引入了距离概念时,我们可以考虑分层图。我们可以把分出k层图,第 i i i 层表示第 i i i天,源点向每一个“地球”连一条容量为无穷大的边,每个空间站向下一时间的该空间站连一条容量为去穷大的边,代表时间间的转移。
参考代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1101 * 50 + 10, M = (N + 1100 + 20 * 1101) + 10, INF = 1e8;
int n, m, k, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
struct Ship
{
int h, r, id[30];
}ships[30];
int p[30];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int get(int i, int day)
{
return day * (n + 2) + i;
}
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 ++ ;
}
bool bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[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 (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit)
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
S = N - 2, T = N - 1;
memset(h, -1, sizeof h);
for (int i = 0; i < 30; i ++ ) p[i] = i;
for (int i = 0; i < m; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
ships[i] = {a, b};
for (int j = 0; j < b; j ++ )
{
int id;
scanf("%d", &id);
if (id == -1) id = n + 1;
ships[i].id[j] = id;
if (j)
{
int x = ships[i].id[j - 1];
p[find(x)] = find(id);
}
}
}
if (find(0) != find(n + 1)) puts("0");
else
{
add(S, get(0, 0), k);
add(get(n + 1, 0), T, INF);
int day = 1, res = 0;
while (true)
{
add(get(n + 1, day), T, INF);
for (int i = 0; i <= n + 1; i ++ )
add(get(i, day - 1), get(i, day), INF);
for (int i = 0; i < m; i ++ )
{
int r = ships[i].r;
int a = ships[i].id[(day - 1) % r], b = ships[i].id[day % r];
add(get(a, day - 1), get(b, day), ships[i].h);
}
res += dinic();
if (res >= k) break;
day ++ ;
}
printf("%d\n", day);
}
return 0;
}
当点的使用次数有限制时,可以把该点拆成一个入点, 一个出点,在入点到处点之间连一条边,容量为该点的使用次数。
建图大致如下:
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1e3 + 10, M = 1e5 + 10, inf = 1e18;
int n, F, D, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
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 ++;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S;
d[S] = 0;
cur[S] = h[S];
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int ver = e[i];
if(d[ver] == -1 && f[i]){
cur[ver] = h[ver];
d[ver] = d[t] + 1;
if(ver == T){
return true;
}
q[++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if(u == T){
return limit;
}
int flow = 0;
for(int i = cur[u]; i != -1 && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if(d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(limit - flow, f[i]));
if(!t){
d[ver] = -1;
}
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while(bfs()){
while(flow = find(S, inf)){
r += flow;
}
}
return r;
}
signed main(){
scanf("%lld%lld%lld",&n, &F, &D);
S = 0;
T = 2 * n + F + D;
memset(h, -1, sizeof(h));
for(int i = 2 * n + 1; i <= 2 * n + F; i ++){
add(S, i, 1);
}
for(int i = 2 * n + F + 1; i <= 2 * n + F + D; i ++){
add(i, T, 1);
}
for(int i = 1; i <= n ; i ++){
add(i, n + i, 1);
int a, b, t;
scanf("%lld%lld",&a, &b);
while(a --){
scanf("%lld", &t);
add(n * 2 + t, i , 1);
}
while(b --){
scanf("%lld", &t);
add(i + n, n * 2 + F + t, 1);
}
}
printf("%lld\n", dinic());
}
由题意得:第一问直接二维dp求最长不下降子序列长度即可。第二问,每个点只能受用一次,问最长不下降子序列有多少个,首先每个点只能使用一次,即对点数有限制,我们对每个点进行拆点。用 f [ i ] f[i] f[i] 表示 以第 i i i 个数结尾,最长不下降子序列的长度,当 j < i j < i j<i a [ j ] < = a [ i ] a[j] <= a[i] a[j]<=a[i] 并且 $f[j] == f[i] - 1 $ 则从 j j j 连一条边到 i i i , 从源点S 向所有 f [ i ] = 1 f[i] = 1 f[i]=1的点连一条边权为1边, 从所有 f [ i ] = = m a x l e n f[i] == maxlen f[i]==maxlen 的点连一条边到汇点t, 再从每个入点连一条边权为1的边到出点。第三问只需要将第二问中的,限制改为无限制(边权为无穷大)即可。
参考代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1010, M = 3e5+10, inf = 1e18;
int n, S, T;
int h[N], e[M], ne[M], f[M], idx;
int a[N], F[N];
int d[N], q[N], cur[N];
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 ++;
}
bool bfs(){
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S;
d[S] = 0;
cur[S] = h[S];
while(hh <= tt){
int t = q[hh ++];
for(int i = h[t]; i != -1; i = ne[i]){
int ver = e[i];
if(d[ver] == -1 && f[i]){
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if(ver == T){
return true;
}
q[++ tt] = ver;
}
}
}
return false;
}
int find(int u, int limit){
if(u == T){
return limit;
}
int flow = 0;
for(int i = cur[u]; i != -1 && flow < limit; i = ne[i]){
cur[u] = i;
int ver = e[i];
if(d[ver] == d[u] + 1 && f[i]){
int t = find(ver, min(limit - flow, f[i]));
if(!t){
d[ver] = -1;
}
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic(){
int r = 0, flow;
while(bfs()){
while(flow = find(S, inf)){
r += flow;
}
}
return r;
}
signed main(){
scanf("%lld",&n);
memset(h, -1, sizeof(h));
for(int i = 1; i <= n ;i ++){
scanf("%lld",&a[i]);
}
S = 0;
T = 2 * n + 1;
int s = 0;
for(int i = 1; i <= n ; i ++){
add(i, n + i, 1);
F[i] = 1;
for(int j = 1; j < i ; j ++){
if(a[j] <= a[i]){
F[i] = max(F[i], F[j] + 1);
}
}
for(int j = 1; j < i ; j ++){
if(a[j] <= a[i] && F[j] + 1 == F[i]){
add( n+j, i, 1);
}
}
s = max(s, F[i]);
if(F[i] == 1){
add(S, i, 1);
}
}
for(int i = 1; i <= n ; i ++){
if(F[i] == s){
add(n + i, T, 1);
}
}
printf("%lld\n", s);
if(s == 1){
printf("%lld\n%lld\n", n, n);
}
else{
int res = dinic();
printf("%lld\n", res);
for(int i = 0; i < idx ; i += 2){
int a = e[i ^ 1];
int b = e[i];
if(a == S && b == 1){
f[i] = inf;
}
else if(a == 1 && b == n + 1){
f[i] = inf;
}
else if(a == n && b == 2 * n){
f[i] = inf;
}
else if(a == 2 * n && b == T){
f[i] = inf;
}
}
printf("%lld\n", res + dinic());
}
}
由题意得:蜥蜴从起始柱子跳出边界时就算逃脱,直接从所有到边界距离小于等于D的柱子的出点向汇点连一条边权为无穷大的点(因为当能够流到柱子的出点时表示能够作为起跳点)。从每个柱子的入点向出点连一条边权为该柱子起始高度的边,从源点向所有初始时刻有蜥蜴的柱子连一条边权为蜥蜴个数边。
参考代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <map>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e3 + 10, M = 2e5 + 10, inf = 1e18;
const double eps = 1e-8;
int h[N], e[M], ne[M], f[M], idx;
int q[N], d[N], cur[N];
int n, m, S, T, cnt_n, cnt_m;
PII zhuzi[M];
double D;
map<PII, int> pp;
string xiyi1[N], high1[N];
double check(PII a, PII b)
{
double dx = a.first - b.first;
double dy = a.second - b.second;
return dx * dx + dy * dy < D * D + 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++;
}
bool bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof(d));
q[0] = S;
d[S] = 0;
cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh++];
for (int i = h[t]; i != -1; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T)
{
return true;
}
q[++tt] = ver;
}
}
}
return false;
}
int find(int u, int limit)
{
if (u == T)
{
return limit;
}
int flow = 0;
for (int i = cur[u]; i != -1 && flow < limit; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(limit - flow, f[i]));
if (!t)
{
d[ver] = -1;
}
f[i] -= t;
f[i ^ 1] += t;
flow += t;
}
}
return flow;
}
int dinic()
{
int r = 0, flow;
while (bfs())
{
while (flow = find(S, inf))
{
r += flow;
}
}
return r;
}
signed main()
{
cin >> n >> m >> D;
memset(h, -1, sizeof(h));
for (int i = 1; i <= n; i++)
{
cin >> high1[i];
high1[i] = " " + high1[i];
}
for (int i = 1; i <= n; i++)
{
cin >> xiyi1[i];
xiyi1[i] = " " + xiyi1[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (high1[i][j] >= '1' && high1[i][j] <= '9')
{
zhuzi[++cnt_n] = {i, j};
pp[{i, j}] = cnt_n;
}
}
}
S = 0;
T = cnt_n * 2 + 1;
for(int i = 1; i <= cnt_n; i ++){
if(zhuzi[i].first <= D || zhuzi[i].first + D > n || zhuzi[i].second <= D || zhuzi[i].second + D > m){
add(i + cnt_n, T, inf);
}
add(i, i + cnt_n, (high1[zhuzi[i].first][zhuzi[i].second] - '0'));
}
int sum = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
if (xiyi1[i][j] == 'L')
{
sum ++;
add(S, pp[{i, j}], 1);
}
}
}
for(int i = 1; i <= cnt_n; i ++){
for(int j = 1; j <= cnt_n; j ++){
if(i == j){
continue;
}
if(check(zhuzi[i], zhuzi[j])){
add(i + cnt_n, j, inf);
}
}
}
int ans = dinic();
//printf("%lld\n", dinic());
printf("%lld\n", sum - ans);
/*
for(int i = 1; i <= n ; i ++){
cout << high1[i] << endl;
}
for(int i = 1; i <= n ; i ++){
cout << xiyi1[i] << endl;
}
*/
}