网络流24题

飞行员配对

(dinic) 跑二分图最大匹配,然后输出方案

const int N = 300 + 5;
const int M = 100000;
int head[N], ver[M], nxt[M], edge[M], d[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x],head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
    memset(d, 0, sizeof d);
    while(q.size())q.pop();
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t)return true;
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i&&rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int main() {
    scanf("%d%d",&m,&n);
    tot = 1;
    int x, y, z;
    while(scanf("%d%d",&x,&y) == 2 && x != -1 && y != -1){
        add(x, y, 1);
    }
    s = n + m + 1, t = n + m + 2;
    for(int i=1;i<=m;i++)add(s, i, 1);
    for(int i=m+1;i<=m+n;i++) add(i, t, 1);
    int flow = 0;
    while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
    printf("%d
",maxflow);
    for(int i=1;i<=m;i++){
        for(int j=head[i];j;j=nxt[j]){
            if(ver[j] == s || edge[j])continue;
            printf("%d %d
",i, ver[j]);
            break;
        }
    }
    return 0;
}

圆桌问题

s 向每个桌子连流量为(c[i]) 的边,每个桌子向每个团队连流量为 (1) 的边,每个团队向 (t) 连流量为 (r[i]) 的边。跑最大流

typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 500 + 5;
const int M = 200000;
int head[N], ver[M], edge[M], nxt[M], d[N];
int r[N], c[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
    ver[++tot]=x,edge[tot]=0,nxt[tot]=head[y],head[y]=tot;
}
bool bfs(){
    while(q.size())q.pop();
    memset(d, 0, sizeof d);
    d[s] = 1;q.push(s);
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t)return true;
            }
        }
    }
    return false;
}

int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(edge[i], flow));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int main() {
    scanf("%d%d",&m,&n);
    int sum = 0;
    tot = 1;
    for(int i=1;i<=m;i++)scanf("%d",&r[i]),sum+=r[i];
    for(int i=1;i<=n;i++)scanf("%d",&c[i]);
    s = n + m + 1, t = n + m + 2;
    for(int i=1;i<=n;i++) add(s, i, c[i]);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) add(i,j+n,1);
    for(int i=1;i<=m;i++) add(i+n, t, r[i]);
    int flow = 0;
    while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
    if(maxflow != sum){
        puts("0");return 0;
    }
    puts("1");
    for(int x=n+1;x<=n+m;x++){
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] == 0)continue;
            printf("%d ", ver[i]);
        }
        puts("");
    }
    return 0;
}

最小路径覆盖问题

拆点,建图,跑最大流

const int inf = 0x3f3f3f3f;
const int N = 400 + 5;
const int M = 20010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int vis[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
    memset(d, 0, sizeof d);
    while(q.size())q.pop();
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                q.push(ver[i]);
                d[ver[i]] = d[x] + 1;
                if(ver[i] == t) return 1;
            }
        }
    }
    return 0;
}
int dinic(int x, int flow){
    if(x == t) return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest; i=nxt[i]){
       	if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

void get(int x){
    int t = x;
    bool flag;
    do{
        printf("%d ", t);
        vis[t] = 1;
        flag = false;
        for(int i=head[t];i;i=nxt[i]){
            if(ver[i] == s || edge[i])continue;
            t = ver[i] - n;
            flag = true;
            break;
        }
    }while(flag);
    puts("");
}
int main() {
    scanf("%d%d",&n,&m);
    tot = 1;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        add(x, y + n, 1);
    }
    s = 2 * n + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        add(s, i, 1);
        add(i+n, t, 1);
    }
    int flow = 0;
    while(bfs())
        while(flow = dinic(s, inf)) maxflow += flow;
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            get(i);
        }
    }
    printf("%d
", n - maxflow);
    return 0;
}

最长不下降子序列

特判n=1的情况

(d[i]) 为以 i 结尾的最长不下降子序列长度

(d[i] == 1), 则 (s -> i) 连一条边

(d[j] + 1 == d[i] ~& ~ a[j] <= a[i]),则 $j' -> i $连一条边

(d[i] == maxlen), 则$ i‘ -> t$ 连一条边

$i -> i' $连一条边

边权都为1,跑最大流

第三问要修改 ((s, 1), (1,1'), (n, n'), (n', t)) 边权为(infin)

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 1000 + 5;
const int M = 600000;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N],a[N],v[N];
int n, s, t, maxflow;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
    memset(d, 0, sizeof d);
    memset(v, 0, sizeof v);
    while(q.size())q.pop();
    q.push(s), d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t) return true;
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    int mx = 0;
    tot = 1;
    for(int i=1;i<=n;i++){
        d[i] = 1;
        for(int j=1;j<i;j++){
            if(a[i] >= a[j]) {
                d[i] = max(d[i], d[j]+1);
            }
        }
        mx = max(mx, d[i]);
    }
    s = 2 * n + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        if(d[i] == 1) add(s, i, 1);
        if(d[i] == mx) add(i+n, t, 1);
        add(i, i+n, 1);
        for(int j=1;j<i;j++){
            if(a[i] >= a[j] && d[i] == d[j] + 1){
                add(j+n, i, 1);
            }
        }
    }
    int flow = 0;
    while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
    printf("%d
", mx);
    printf("%d
", maxflow);
    for(int i=head[s];i;i=nxt[i])if(ver[i] == 1) edge[i] = inf;
    for(int i=head[1];i;i=nxt[i])if(ver[i] == 1+n) edge[i] = inf;
    for(int i=head[n];i;i=nxt[i]) if(ver[i] == 2*n) edge[i] = inf;
    for(int i=head[2*n];i;i=nxt[i]) if(ver[i] == t) edge[i] = inf;
    while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
    if(n == 1) maxflow = 1;
    printf("%d
", maxflow);
    return 0;
}

方格取数(最大点权独立集)

  1. 满足每一条边的两个端点至少选一个的最小权点集
  2. 满足每一条边的两个端点至多选一个是最大权点集

这两个集合互为补集,二分图左部点与 s 连边权为该点点权的边,右部与 t 连边权为该点点权的点,原二分图的边边权设置为(infin) ,跑最小割(最大流),被割掉的那些边,就是不能选的边(即一一对应原来二分图中的点)。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 10000 + 5;
const int M = 100000;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N], a[101][101],v[N];
int n, m, s, t, maxflow;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
    memset(d, 0, sizeof d);
    memset(v, 0, sizeof v);
    while(q.size())q.pop();
    q.push(s), d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t) return true;
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
inline int id(int x, int y){return (x-1)*m+y;}

int main() {
    scanf("%d%d",&n,&m);
    s = n * m + 1, t = s + 1;
    tot = 1;
    int sum = 0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            if((i+j)%2 == 0){
                add(s, id(i, j), a[i][j]);
                if(i > 1) add(id(i, j), id(i-1, j), inf);
                if(j > 1) add(id(i, j), id(i, j-1), inf);
                if(i < n) add(id(i, j), id(i+1, j), inf);
                if(j < m) add(id(i, j), id(i, j+1), inf);
            }
            else add(id(i, j), t, a[i][j]);
            
            sum += a[i][j];
        }
    }
    int flow = 0;
    while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
    sum -= maxflow;
    printf("%d
",sum);
    return 0;
}
/*
3 3
1 2 3
3 2 3
2 3 1 
*/

太空飞行计划问题

最大权闭合图,选谁必选谁

S 向正权点连容量为该点权的边, 负权点向T连容量为该点权绝对值的边,原来的边容量全部为(infin)

最小割之后,与 S 联通点都要选,与T联通的点都不选

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <sstream>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
const int M = 100010;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N], v[N];
int n, m, s, t, maxflow;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
    memset(d, 0, sizeof d);
    memset(v, 0, sizeof v);
    while(q.size())q.pop();
    q.push(s), d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                cout << x << ' ' << ver[i] << endl;
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t) return true;
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
int main() {
    scanf("%d%d",&n,&m);
    getchar();
    tot = 1;s = n + m + 1, t = s + 1;
    int sum = 0;
    for(int i=1;i<=n;i++){
        string t;
        getline(cin, t);
        stringstream ss(t);
        bool flag = true;
        int x = 0;
        while(ss >> x){
            if(flag) {
                sum += x;
                add(s, i, x);
            }
            else{
                add(i, x+n, inf);
            }
            flag = false;
        }
    }
    for(int i=1;i<=m;i++){
        int x;scanf("%d",&x);
        add(i+n, t, x);
    }
    int flow = 0;
    while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
    sum -= maxflow;
    //可以利用最后一次bfs的信息判断是否联通
    bool flag = true;
    for(int i=1;i<=n;i++) if(d[i] && flag) printf("%d",i),flag = false;else if(d[i]) printf(" %d",i);
    puts("");
    flag = true;
    for(int i=1;i<=m;i++) if(d[i+n] && flag) printf("%d",i),flag = false;else if(d[i+n]) printf(" %d",i);
    puts("");
    printf("%d
",sum);
    return 0;
}

负载均衡问题

裸的均分纸牌问题,可以(O(nlog n)) 时间解决

网络流的可以这样考虑,设平均值为 (ave)

对于权值大于等于 (ave) 的点,连接 (s ightarrow i) ,边权为 (a[i] -ave) ,费用(0)

对于权值小于等于(ave) 的点,连接(i ightarrow t) , 边权为(ave-a[i]), 费用 (0)

对于相邻的点:连接(i ightarrow j) ,边权(inf), 费用为 (1)

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;

int n, a[N], s[N];

int main() {
    cin >> n;
    int sum = 0;
    for(int i=1;i<=n;i++) cin >> a[i], sum += a[i];
    sum /= n;
    for(int i=1;i<=n;i++) a[i] -= sum,s[i] = s[i-1] + a[i];
    sort(s + 1, s + 1 + n);
    int res = 0;
    for(int i=1;i<=n;i++) res += abs(s[i] - s[(n+1)/2]);
    cout << res << endl;
    return 0;
}

软件补丁问题

(spfa)或者(dijkstra)

边数过多可以不加边,直接在转移时判断

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 20;
const int M = 101;
int n, m, s, t;
int d[1<<20], v[1<<20];
int c[M], b1[M], b2[M], f1[M], f2[M];
char str[101];
void dijkstra(){
    memset(d, 0x3f,sizeof d);
    priority_queue<pair<int,int> > q;
    q.push({0, s});d[s] = 0;
    while(q.size()){
        int x = q.top().second;q.pop();
        if(v[x])continue;
        v[x] = 1;
        for(int i=1;i<=m;i++){
            if((x&b1[i]) == b1[i] && (x&b2[i]) == 0){
                int y = ((x|f1[i])^f1[i]) | f2[i];
                if(d[y] > d[x] + c[i]){
                    d[y] = d[x] + c[i];
                    q.push({-d[y], y});
                }
            }
        }
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&c[i]);
        scanf("%s",str);
        for(int j=0;j<n;j++)if(str[j] == '+')b1[i] |= 1 << j;else if(str[j] == '-') b2[i] |= 1 << j;
        scanf("%s",str);
        for(int j=0;j<n;j++)if(str[j] == '-')f1[i] |= 1 << j;else if(str[j] == '+') f2[i] |= 1 << j;
    }
    s = (1 << n) - 1;
    dijkstra();
    if(d[t] == inf)puts("0");
    else printf("%d
",d[t]);
    return 0;
}

孤岛营救问题

(e[x1][y1][x2][y2]) 表示((x1,y1) ightarrow (x2,y2)) 门的情况,-1表示畅通,0表示不可逾越,否则状压表示所需的钥匙

(d[x][y][i]) 表示到达((x,y)) 位置,拥有钥匙状态为 i 时的最短时间

(key[x][y]) 表示 ((x,y)) 位置的钥匙情况

由于转移条件每次只会增加1的时间,所以可以直接(bfs)

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 11;
int e[N][N][N][N], key[N][N];
int d[N][N][1<<N], v[N][N][1<<N];
int n, m, p, k, s;
queue<pair<pair<int,int>,int>>q;
int dx[4] = {0,0,-1,1}, dy[4] = {1,-1,0,0};
#define mk make_pair
int dijkstra(){
    memset(d, 0x3f, sizeof d);
    q.push(mk(mk(1,1),key[1][1]));
    d[1][1][key[1][1]] = 0;
    while(q.size()){
        auto t = q.front();q.pop();
        int x = t.first.first, y = t.first.second, keys = t.second;
        for(int i=0;i<4;i++){
            int nx = x + dx[i], ny = y + dy[i];
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
            if(e[x][y][nx][ny] == 0)continue;
            if(e[x][y][nx][ny] > 0 && (keys & e[x][y][nx][ny]) == 0)continue;
            int nkeys = keys | key[nx][ny];
            //printf("(%d,%d,%d)->(%d,%d,%d)
",x,y,keys,nx,ny,nkeys);
            if(v[nx][ny][nkeys])continue;
            d[nx][ny][nkeys] = d[x][y][keys] + 1;
            v[nx][ny][nkeys] = 1;
            q.push(mk(mk(nx,ny),nkeys));
            if(nx == n && ny == m) return d[nx][ny][nkeys];
        }
    }
    return -1;
}
int main() {
    scanf("%d%d%d%d",&n,&m,&p,&k);
    memset(e, -1, sizeof e);
    for(int i=1;i<=k;i++){
        int x1,y1,x2,y2,t;
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&t);
        if(t == 0) 
            e[x1][y1][x2][y2] = e[x2][y2][x1][y1] = t;
        else e[x1][y1][x2][y2] = e[x2][y2][x1][y1] = 1 << (t-1);
    }
    scanf("%d",&s);
    for(int i=1;i<=s;i++){
        int x, y, t;
        scanf("%d%d%d",&x,&y,&t);
        key[x][y] |= 1 << (t-1);
    }
    printf("%d
",dijkstra());
    return 0;
}

星际转移

首先用并查集判断是否联通,然后确定有没有解。

猜想答案不会很大,所以每次增加一次时间,就多建一层图,从源点向所有的地球连inf的边,由于人类可以停留不动,所以上一层到这一层的对应节点之间连inf的边,然后上一层与这一层可以发生太空船转移的,加h[i]容量的边

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 1000000 + 5;
const int M = 1000010;
int head[N], ver[M], nxt[M], edge[M], d[N], tot;
int f[50], h[50], num[50], g[50][50]; 
int n, m, k, s, t;
int maxflow, flow, res;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
int find(int x){return x == f[x] ? x : f[x] = find(f[x]);}
void merge(int x, int y){
    x = find(x), y = find(y);
    if(x != y) f[x] = y;
}
bool bfs(){
    for(int i=0;i<=res*(n+1);i++) d[i] = 0;
    d[t] = 0;
    queue<int> q;
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                q.push(ver[i]);
                if(ver[i] == t) return true;
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t) return flow;
    int rest = flow, k;
    for(int i=head[x];i&&rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(edge[i], rest));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i^1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
int main() {
    scanf("%d%d%d",&n,&m,&k);
    s = 0, t = 1000000;
    for(int i=1;i<=n+2;i++)f[i] = i;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&h[i], &num[i]);
        for(int j=0;j<num[i];j++){
            scanf("%d",&g[i][j]);
            if(g[i][j] == -1) g[i][j] = n+2;
            if(g[i][j] == 0) g[i][j] = n+1;
            if(j) merge(g[i][j-1], g[i][j]);
        }
    }
    if(find(n+1) != find(n+2)){
        puts("0");
        return 0;
    }
    tot = 1;
    for(res=1;;res++){
        add(s, res * (n+1), inf);
        for(int i=1;i<=m;i++){
            int x = (res-1)%num[i], y = res % num[i];
            if(g[i][x] == n+2) x = t;
            else x = (res-1)*(n+1) + g[i][x];
            if(g[i][y] == n+2) y = t;
            else y = res*(n+1) + g[i][y];
            add(x, y, h[i]);
        }
        while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
        if(maxflow >= k){
            printf("%d
", res);
            return 0;
        }
        for(int i=1;i<=n+1;i++)add((res-1)*(n+1)+i, res*(n+1)+i, inf);
    }
    return 0;
}

餐巾计划

每一天要产生 (r_i) 个脏餐巾 -> s向i连流量为(r_i),费用为0的边

每一天要使用 (r_i) 个餐巾 -> i'向t连流量为(r_i),费用为0的边

每一天可以购买餐巾,s 向 i' 连流量为inf,费用为p的边

脏的餐巾可以送到快洗店 -> i 向 (i+m)' 连流量为inf,费用为f的边

脏的餐巾可以送到慢洗店 -> i 向 (i+n)' 连流量为inf,费用为s的边

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 4000 + 5;
const int M = 1000010;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int pre[N], d[N], incf[N],  v[N];
int n, k, tot, s, t, p, kx, mx, fkx, fmx;
ll maxflow, ans;
void add(int x,int y,int z,int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] > d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == inf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += 1ll * d[t] * incf[t];
}
int main() {
    scanf("%d",&n);
    tot = 1, s = 2 * n + 1, t = s + 1;
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        add(s, i, x, 0);
        add(i+n, t, x, 0);
    }
    scanf("%d%d%d%d%d",&p,&kx,&fkx,&mx,&fmx);
    for(int i=1;i<=n;i++){
        add(s, i+n, inf, p);
        if(i < n) add(i, i+1, inf, 0);
        if(i + kx <= n) add(i, i+kx+n, inf, fkx);
        if(i + mx <= n) add(i, i+mx+n, inf, fmx);
    }
    while(spfa())update();
    printf("%lld
",ans);
    return 0;
}

试题库问题

s 向每种类别连边,流量为其所需的题目数量

每种类别向对应的题目连边,流量为 1

每个题目向 t 连边,流量为 1

跑最大流,如果流量等于所需题目总数则输出方案,否则输出No Solution!

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 100010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int n, m, k, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
    memset(d, 0, sizeof d);
    while(q.size())q.pop();
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                q.push(ver[i]);
                d[ver[i]] = d[x] + 1;
                if(ver[i] == t) return 1;
            }
        }
    }
    return 0;
}
int dinic(int x, int flow){
    if(x == t) return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest; i=nxt[i]){
       	if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}

int main() {
    scanf("%d%d",&k,&n);
    s = k + n + 1, t = s + 1;
    tot = 1;
    for(int i=1,x;i<=k;i++){
        scanf("%d",&x);
        add(s, i, x);
        m+=x;
    }
    for(int i=1,p,x;i<=n;i++){
        scanf("%d",&p);
        add(i+k, t, 1);
        while(p--){
            scanf("%d",&x);
            add(x, i+k, 1);
        }
    }
    int flow = 0;
    while(bfs())while(flow = dinic(s,inf))maxflow += flow;
    if(maxflow < m){
        puts("No Solution!");
        return 0;
    }
    for(int i=1;i<=k;i++){
        printf("%d:", i);
        for(int j = head[i];j;j=nxt[j]){
            if(ver[j] != s && edge[j] == 0){
                printf(" %d", ver[j] - k);
            }
        }
        puts("");
    }
    return 0;
}

最长k可重线段集问题

此题与最长 k 可重区间集问题有很大的相似之处,唯有一点不同的是,线段可以垂直于 x 轴,所以我们不能让 x -> x连边产生自环,所以对点进行拆点,每个点分为入状态和出状态两个状态。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 1000010;
int n, k, l[N], r[N], l1[N], r1[N];
int ver[M], edge[M], nxt[M], head[N];
int incf[N], pre[N], v[N];
int tot, s, t;
ll cost[M], d[N], maxflow, ans, len[N];
vector<int> all;
void add(int x,int y,int z,ll c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] > d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == 0x3f3f3f3f3f3f3f3f)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}
ll S(ll x){return x * x;}
ll calc(int id){
    return sqrt(S(l[id]-l1[id]) + S(r[id]-r1[id]));
}
int main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d%d",&l[i],&r[i],&l1[i], &r1[i]);
        if(l[i] > l1[i]) swap(l[i], l1[i]);
        len[i] = calc(i);
        l[i] <<= 1;
        l1[i] <<= 1;
        if(l[i] == l1[i]) l1[i]++; else l[i] ++;//这里的操作就是拆点操作,奇数点为出点,偶数点为入点
        all.push_back(l[i]);all.push_back(l1[i]);
    }

    sort(all.begin(),all.end());
    tot = 1, s = all.size(), t = s + 1;
    add(s, 0, k, 0);
    add(s-1, t, k, 0);
    for(int i=0;i<all.size() - 1;i++){
        add(i, i + 1, inf, 0);
    }
    for(int i=1;i<=n;i++){
        int lid = lower_bound(all.begin(),all.end(),l[i]) - all.begin();
        int rid = lower_bound(all.begin(), all.end(), l1[i]) - all.begin();
        add(lid, rid, 1, -len[i]);
    }
    while(spfa())update();
    cout << -ans << endl;
    return 0;
}

最长k可重区间集问题

设值域范围为 [a,b] ,则 s 向 a 连流量 k,费用0的边,b 向 t 连流量 k,费用 0 的边,从 s 走到 t,[a,b) 中的每个点 i ,都向 i+1 连流量为 inf,费用为 0 的边。对于每一个输入给定的区间 [l,r],l 向 r 连流量为 1,费用为 r-l 的边,然后跑最大费用最大流即可。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 1000010;
int n, k, l[N], r[N], id[N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int tot, s, t, maxflow, ans;
vector<int> all;
void add(int x,int y,int z,int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] > d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == inf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}

int main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&l[i],&r[i]);
        if(l[i] > r[i]) swap(l[i], r[i]);
        all.push_back(l[i]);all.push_back(r[i]);
    }
    sort(all.begin(),all.end());
    tot = 1, s = all.size(), t = s + 1;
    add(s, 0, k, 0);
    add(s-1, t, k, 0);
    for(int i=0;i<all.size() - 1;i++){
        add(i, i + 1, inf, 0);
    }
    for(int i=1;i<=n;i++){
        int lid = lower_bound(all.begin(),all.end(),l[i]) - all.begin();
        int rid = lower_bound(all.begin(), all.end(), r[i]) - all.begin();
        add(lid, rid, 1, l[i]-r[i]);
    }
    while(spfa())update();
    cout << -ans << endl;
    return 0;
}

火星探险问题

将每个点拆为入点和出点,对于不是障碍的每个入点向出点连流量为 inf,费用为 0 的边,如果该点是岩石,则再连 流量为 1,费用为 1 的边

s 连 (1,1) 的入点, 流量为 k,费用为 0

(n,m) 的出点连 t 的出点,流量为 k, 费用 0

最后要输出方案,从 s 走到 t 走最大流的个数次,每次从出点遍历边,当该边的反向边流量不为 1 时,则从该边去走,同时将该反向边的流量减 1

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 2500 + 5;
const int M = 1000010;
int head[N], ver[M], edge[M], cost[M], nxt[M];
int d[N], incf[N], pre[N], v[N];
int n, m, k, tot, s, t, maxflow, ans, sum;
int a[50][50];
inline int id(int x, int y){return x * m + y;}
void add(int x, int y, int z, int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0xcf, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for(int i=head[x];i;i=nxt[i]){
            if(!edge[i]) continue;
            int y = ver[i];
            if(d[y] < d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y]) v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == 0xcfcfcfcf)return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i^1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += incf[t] * d[t];
}
void print(int cur){
    int x = id(0,0) + sum;
    while(x != id(n-1, m-1) + sum){
        int y;
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i^1]){
                edge[i^1]--;
                y = ver[i];
                break;
            }
        }
        if(y == x - sum + 1){ // 向右走
            printf("%d %d
", cur, 1);
        }
        else printf("%d %d
", cur, 0);
        x = y + sum;
    }
}
int main() {
    scanf("%d%d%d",&k,&m,&n);
    tot = 1, s = 2 * n * m + 1, t = s + 1;
    sum = n * m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%d",&a[i][j]);
            if(a[i][j] == 0 || a[i][j] == 2) add(id(i,j), id(i,j) + sum, inf, 0);
            if(a[i][j] == 2) add(id(i,j), id(i,j) + sum, 1, 1);
        }
    }
    add(s, id(0, 0), k, 0);
    add(id(n-1, m-1) + sum, t, k, 0);
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(i < n-1) add(id(i,j)+sum, id(i+1,j), inf, 0);
            if(j < m-1) add(id(i,j)+sum, id(i,j+1), inf, 0);
        }
    }
    while(spfa()) update();
    //cout << maxflow << endl;
    for(int i=1;i<=maxflow;i++){
        print(i);
    }
    return 0;
}

深海机器人问题

这个题建图稍微有些复杂,需要理清楚当前输入的哪条边,只要仔细建好图,这个题就是不难的。(给出的起点和终点坐标不只是反的还是正的,就直接面向数据编程了)

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1000 + 5;
const int M = 1000010;
int P, Q, A, B;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
    //dbg(x,y,z,c);
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
    queue<int> q;
    memset(d, 0xcf, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] < d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == 0xcfcfcfcf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}
int id(int x, int y){
    return x * (Q+1) + y;
}

int main() {
    scanf("%d%d%d%d",&A,&B, &P, &Q);
    tot = 1;
    s = (P+1)*(Q+1), t = s + 1;
    for(int i=0;i<P+1;i++){
        for(int j=0;j<Q;j++){
            int x;scanf("%d",&x);
            add(id(i, j), id(i, j+1), 1, x);
            add(id(i, j), id(i, j+1), inf, 0);
        }
    }
    for(int j=0;j<Q+1;j++){
        for(int i=0;i<P;i++){
            int x;scanf("%d",&x);
            add(id(i,j), id(i+1,j), 1, x);
            add(id(i,j), id(i+1,j), inf, 0);
        }
    }
    for(int i=1;i<=A;i++){
        int k, x, y;
        scanf("%d%d%d",&k,&x,&y);
        //swap(x,y);
        add(s, id(x, y), k, 0);
    }
    for(int i=1;i<=B;i++){
        int k, x, y;
        scanf("%d%d%d",&k,&x,&y);
        //swap(x,y);
        add(id(x,y), t, k, 0);
    }
    while(spfa())update();
    printf("%d
",ans);
    return 0;
}

航空路线问题

这个题可以理解成从s 到 t 找两个不交叉的最长路径,拆点,x 向 x' 连流量为1,费用为1的边,s 向 s' 连流量为2,费用为0的边,t 向 t' 连流量为2,费用为0的边,原图的边(u,v),从 u' 向 v 连流量为1,费用为0的边, 注意一点的是如果 u = s, v = t, 则该边流量要设置为 2。从 s 到 t跑最大费用最大流, 然后输出方案即可。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 200 + 5;
const int M = 200010;
unordered_map<string,int> mp;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, m, k, tot, s, t, maxflow, ans;
string name[101];
void add(int x,int y,int z,int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
    queue<int> q;
    memset(d, 0xcf, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] < d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == 0xcfcfcfcf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}

int main() {
    scanf("%d%d",&n,&m);
    tot = 1, s = 2 * n + 1, t = s + 1;
    add(s, 1+n, 2, 0);
    add(n, t, 2, 0);
    for(int i=1;i<=n;i++){
        cin >> name[i];
        mp[name[i]] = i;
        add(i, i+n, 1, 1);
    }
    for(int i=1;i<=m;i++){
        string x,y;cin >> x >> y;
        if(mp[x] > mp[y]) swap(x, y);
        if(mp[x] == 1 && mp[y] == n)
            add(mp[x] + n, mp[y], 2, 0);
        else add(mp[x] + n, mp[y], 1, 0);
    }
    while(spfa()) update();
    if(maxflow != 2){
        puts("No Solution!");
        return 0;
    }
    printf("%d
", ans + 2);
    cout << name[1] << endl;
    memset(v, 0, sizeof v);
    int p = 1 + n;
    while(p != n+n){
        for(int i=head[p];i;i=nxt[i]){
            if(edge[i] == 0 && ver[i] <= n && !v[ver[i]]){
                v[ver[i]] = 1;
                p = ver[i];break;
            }
        }
        cout << name[p] << endl;
        p = p + n;
    }
    p = n;
    while(p != 1){
        for(int i=head[p];i;i=nxt[i]){
            if(edge[i] != 0 && ver[i] <= 2 * n && !v[ver[i] - n]){
                v[ver[i]-n] = 1;
                p = ver[i] - n;break;
            }
        }
        cout << name[p] << endl;
    }
    return 0;
}

数字梯形问题

洛谷区题解

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1610;
const int M = 1000010;
int n, m, a[N][N], sum, id[N][N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
    //dbg(x,y,z,c);
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0xcf, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] < d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    //cout << d[t] << endl;
    if(d[t] == 0xcfcfcfcf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}

void clear(){
    memset(head, 0, sizeof head);
    maxflow = 0;
    tot = 1;
}
void solve1(){
    clear();
    s = 2 * sum + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        for(int j = 1; j <= m + i - 1; j++){
            if(i == 1) add(s, id[i][j], 1, 0);
            add(id[i][j], id[i][j]+sum, 1, a[i][j]);
            if(i < n){
                add(id[i][j] + sum, id[i+1][j], 1, 0);
                add(id[i][j] + sum, id[i+1][j+1], 1, 0);
            }else add(id[i][j]+sum, t, 1, 0);
        }
    }
    //for(int i=1;i<=m+n-1;i++) add(id[n][i] + sum, t, 1, 0);
    ans = 0;
    while(spfa()) update();
    printf("%d
",ans);
}
void solve2(){
    clear();
    s = sum + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m+i-1;j++){
            if(i == 1) add(s, id[i][j], 1, a[i][j]);
            if(i < n) {
                add(id[i][j], id[i+1][j], 1, a[i+1][j]);
                add(id[i][j], id[i+1][j+1], 1, a[i+1][j+1]);
            } else {
                add(id[i][j], t, inf, 0);
            }
        }
    }
    ans = 0;
    while(spfa()) update();
    printf("%d
",ans);
}
void solve3(){
    clear();
    s = sum + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m+i-1;j++){
            if(i == 1) add(s, id[i][j], 1, a[i][j]);
            if(i < n) {
                add(id[i][j], id[i+1][j], inf, a[i+1][j]);
                add(id[i][j], id[i+1][j+1], inf, a[i+1][j+1]);
            } else {
                add(id[i][j], t, inf, 0);
            }
        }
    }
    ans = 0;
    while(spfa()) update();
    printf("%d
",ans);
}
int main() {
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m+i-1;j++){
            scanf("%d",&a[i][j]);
            id[i][j] = ++sum;
        }   
    }
    solve1();
    solve2();
    solve3();
    return 0;
}

骑士共存问题

互斥的位置,横坐标与纵坐标和的奇偶性不同,所以最终连接好的是一个二分图,将互斥关系建图,跑最大流求出最小割,含义是需要割掉多少条边使得不互斥,能割掉的只有从 s, 和 t 连接的边,所以把互斥关系的边流量设置为 inf,这样就可以表示要最多取消放置多少个骑士,使得图中不存在互斥关系。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
#define dbg(x...) do { cout << "33[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "33[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int inf = 0x3f3f3f3f;
const int N = 40000 + 5;
const int M = 1000010;
int n, m, a[202][202];
int s, t, head[N], ver[M], nxt[M], edge[M], tot;
int d[N],maxflow;

void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
int dx[8] = {-1,-2,-2,-1,1,2,2,1};
int dy[8] = {-2,-1,1,2,2,1,-1,-2};
int id(int x, int y){return (x-1)*n + y;}
void add(int x, int y){
    if(a[x][y])return;
    if((x+y) & 1){
        add(id(x,y), t, 1);
        return ;
    }
    add(s, id(x, y), 1);
    for(int i=0;i<8;i++){
        int nx = x + dx[i], ny = y + dy[i];
        if(nx < 1 || nx > n || ny < 1 || ny > n || a[nx][ny])continue;
        add(id(x,y), id(nx,ny), 1);
    }
}
bool bfs(){
    queue<int> q;
    memset(d,0,sizeof d);
    q.push(s), d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                d[ver[i]] = d[x] + 1;
                if(ver[i] == t)return true;
                q.push(ver[i]);
            }
        }
    }
    return false;
}
int dinic(int x, int flow){
    if(x == t)return flow;
    int rest = flow, k;
    for(int i=head[x];i&&rest;i=nxt[i]){
        if(edge[i] && d[ver[i]] == d[x] +1){
            k = dinic(ver[i], min(edge[i], rest));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
int main() {
    scanf("%d%d",&n,&m);
    int sum = n * n;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        a[x][y] = 1;
        sum --;
    }
    tot = 1, s = n * n + 1, t = s + 1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            add(i, j);
        }
    }
    int flow;
    while(bfs())while(flow=dinic(s, inf))maxflow+=flow;
    sum -= maxflow;
    printf("%d
", sum);
    return 0;
}

运输问题

供需关系建图,跑最小费用最大流即可

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 200 + 5;
const int M = 100010;
int n,m,a[N], b[N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N], e[N][N];
int k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
    queue<int> q;
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push(s);
    d[s] = 0, v[s] = 1;
    incf[s] = inf;
    while(q.size()){
        int x = q.front();
        q.pop();
        v[x] = 0;
        for (int i = head[x]; i;i=nxt[i]){
            if(!edge[i])
                continue;
            int y = ver[i];
            if(d[y] > d[x] + cost[i]){
                d[y] = d[x] + cost[i];
                incf[y] = min(incf[x], edge[i]);
                pre[y] = i;
                if(!v[y])
                    v[y] = 1, q.push(y);
            }
        }
    }
    if(d[t] == inf)
        return false;
    return true;
}
void update(){
    int x = t;
    while(x != s){
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i ^ 1] += incf[t];
        x = ver[i ^ 1];
    }
    maxflow += incf[t];
    ans += d[t] * incf[t];
}


int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=m;i++)scanf("%d",&b[i]);
    s = n + m + 1, t = s + 1;
    tot = 1;
    for(int i=1;i<=n;i++){
        add(s, i, a[i], 0);
        for(int j=1,x;j<=m;j++){
            scanf("%d",&e[i][j]);
            add(i, j+n, inf, e[i][j]);
        }
    }
    for(int i=1;i<=m;i++) add(i+n, t, b[i], 0);
    while(spfa()) update();
    printf("%d
", ans);
    memset(head, 0, sizeof head);
    tot = 1;
    for(int i=1;i<=n;i++){
        add(s, i, a[i], 0);
        for(int j=1,x;j<=m;j++){
            add(i, j+n, inf, -e[i][j]);
        }
    }
    for(int i=1;i<=m;i++) add(i+n, t, b[i], 0);
    ans = 0;
    while(spfa()) update();
    printf("%d
", -ans);
    return 0;
}

汽车加油行驶问题

这是个最短路的题,d[x][y][i] 表示从起点到 (x,y) ,油量为i 的最小花费。

注意一点的是如果到达了一个有加油站的地点,那么这时的操作必须要加油,加满了之后才能接着走。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
int a[N][N], n, k, A, B, C;
int d[N][N][11], v[N][N][11];
struct node{
    int x, y, k;
    node(int x,int y, int k):x(x),y(y),k(k){}
};
bool operator <(const node a, const node b){
    return a.x < b.x;    
}
#define mk make_pair
priority_queue<pair<int,node>>q;
int dx[4] = {-1,0,1,0}, dy[4] = {0, -1, 0, 1};
int dijkstra(){
    memset(d, 0x3f, sizeof d);
    memset(v, 0, sizeof v);
    q.push(mk(0, (node){1,1,k}));
    d[1][1][k] = 0;
    while(q.size()){
        auto t = q.top().second;q.pop();
        int x = t.x, y = t.y, ck = t.k;
        if(v[x][y][ck])continue;
        v[x][y][ck] = 1;
        if(ck < k){
            if(a[x][y] == 0){
                if(d[x][y][k] > d[x][y][ck] + A + C){//如果这个地方没有加油站,可以建一个加油站
                    d[x][y][k] = d[x][y][ck] + A + C;
                    q.push(mk(-d[x][y][k], (node){x,y,k}));
                }
            }else{
                if(d[x][y][k] > d[x][y][ck] + A){//加油操作
                    d[x][y][k] = d[x][y][ck] + A;
                    q.push(mk(-d[x][y][k], (node){x,y,k}));
                }
                continue;//这里要continue
            }
        }
        if(ck)
        for(int i=0;i<4;i++){
            int nx = x + dx[i], ny = y + dy[i];
            if(nx < 1 || nx > n || ny < 1 || ny > n)continue;
            int cost = d[x][y][ck];
            if(i < 2) cost += B;
            if(d[nx][ny][ck-1] > cost){
                d[nx][ny][ck-1] = cost;
                q.push(mk(-d[nx][ny][ck-1], (node){nx,ny,ck-1}));
            }
        }
    }
    int res = inf;
    for(int i=0;i<=k;i++) res = min(res, d[n][n][i]);
    return res;
}

int main() {
    scanf("%d%d%d%d%d",&n,&k,&A,&B,&C);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
    }  
    printf("%d
", dijkstra());
    return 0;
}

分配问题

二分图带权最大匹配问题,可以用费用流解决但是复杂度比较玄学,如果边数太多很可能会卡((M = N^2)),边数在500以下时完全可以跑KM,但是最好用BFS版本的KM,复杂度为 (O(n^3)) , DFS版本的KM复杂度一般都是(O(N^4))

可以参考https://www.cnblogs.com/1625--H/p/12468117.html 中的 J-spy

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
ll n, a[N],b[N],c[N],p[N];
ll w[N][N];
ll lx[N] , ly[N];
ll match[N];
ll slack[N];
bool vy[N];
ll pre[N];
void bfs( ll k ){
    ll x , y = 0 , yy = 0 , delta;
    memset( pre , 0 , sizeof(pre) );
    for( ll i = 1 ; i <= n ; i++ ) slack[i] = inf;
    match[y] = k;
    while(1){
        x = match[y]; delta = inf; vy[y] = true;
        for( ll i = 1 ; i <= n ;i++ ){
            if( !vy[i] ){
                if( slack[i] > lx[x] + ly[i] - w[x][i] ){
                    slack[i] = lx[x] + ly[i] - w[x][i];
                    pre[i] = y;
                }
                if( slack[i] < delta ) delta = slack[i] , yy = i ;
            }
        }
        for( ll i = 0 ; i <= n ; i++ ){
            if( vy[i] ) lx[match[i]] -= delta , ly[i] += delta;
            else slack[i] -= delta;
        }
        y = yy ;
        if( match[y] == -1 ) break;
    }
    while( y ) match[y] = match[pre[y]] , y = pre[y];
}
 
ll KM(){
    memset( lx , 0 ,sizeof(lx) );
    memset( ly , 0 ,sizeof(ly) );
    memset( match , -1, sizeof(match) );
    for( ll i = 1 ; i <= n ; i++ ){
        memset( vy , false , sizeof(vy) );
        bfs(i);
    }
    ll res = 0 ;
    for( ll i = 1 ; i <= n ; i++ ){
        if( match[i] != -1 ){
            res += w[match[i]][i] ;
        }
    }
    return res;
}
 
int main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lld",&w[i][j]);
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j] = -w[i][j];
    printf("%lld
",-KM());
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j] = -w[i][j];
    printf("%lld
",KM());
    return 0;
}

魔术球问题

可以想到最终答案不会特别大,因为完全平方数到后面会非常稀疏

所以每次新增一个球,将它与之前可以组合为完全平方数的球连边,在残余网络中跑最大流,当球数量 - 最大流 > n 时,就停止

#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 20000 + 5;
const int maxn = 10000;
const int M = 200010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int vis[N];
int n, m, s, t, tot, maxflow, res;
queue<int> q;
void add(int x, int y, int z){
    ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
    ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
    memset(d, 0, sizeof d);
    while(q.size())q.pop();
    q.push(s);d[s] = 1;
    while(q.size()){
        int x = q.front();q.pop();
        for(int i=head[x];i;i=nxt[i]){
            if(edge[i] && !d[ver[i]]){
                q.push(ver[i]);
                d[ver[i]] = d[x] + 1;
                if(ver[i] == t) return 1;
            }
        }
    }
    return 0;
}
int dinic(int x, int flow){
    if(x == t) return flow;
    int rest = flow, k;
    for(int i=head[x];i && rest; i=nxt[i]){
       	if(edge[i] && d[ver[i]] == d[x] + 1){
            k = dinic(ver[i], min(rest, edge[i]));
            if(!k) d[ver[i]] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            rest -= k;
        }
    }
    return flow - rest;
}
bool isok(int x){
    int s = sqrt(x);
    return s * s == x;
}
void get(int x){
    int t = x;
    bool flag;
    do{
        vis[t] = 1;printf("%d ", t);
        flag = false;
        for(int i=head[t];i;i=nxt[i]){
            if(ver[i] == s || vis[i] == res || edge[i])continue;
            flag = true;t = ver[i]-maxn;break;
        }
    }while(flag);
    puts("");
}
int main() {
    scanf("%d",&n);
    s = 0, t = 20000;
    tot = 1;
    for(int i=1;;i++){
        add(s, i, 1); add(i+maxn, t, 1);
        for(int j=1;j<i;j++){
            if(isok(i+j)) add(j, i + maxn, 1);
        }
        int flow = 0;
        while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
        if(i - maxflow > n){
            res = i - 1;break;
        }
    }
    printf("%d
",res);
    for(int i=1;i<=res;i++){
        if(!vis[i])get(i);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/1625--H/p/12498234.html