洛谷网络流24题

一、P2756   飞行员配对方案问题

二、 P2762  太空飞行计划问题

三、P2764 最小路径覆盖问题

四、P2765 魔术球问题

五、P3254 圆桌问题

六、P2766 最长不下降子序列问题

七、P2763 试题库问题

八、P2774 方格取数问题

九、P1251 餐巾计划问题

十、P2770 航空路线问题

十一、P4011 孤岛营救问题(emmm,没用到网络流,也没找到网络流的题解)

十二、P4013 数字梯形问题

十三、P4015 运输问题

十四、P4014 分配问题

十五、P4016 负载平衡问题

十六、P4012 深海机器人问题

十七、P3357 最长k可重线段集问题

十八、P3358 最长k可重区间集问题

十九、P3356 火星探险问题

二十、P3355 骑士共存问题

 

 

 

 

 

 

下文中的s,t若题目中没有给出,均为自己建的超级源点和汇点

一、P2756   飞行员配对方案问题

思路:点s和所有外籍飞行员连边,权值为1,所有外籍飞行员与对应的英国飞行员建边,权值为1,所有英国飞行员和t建边,权值为1,所有边都建立对应的反边,权值为0,Dinic跑一下,加过就是最大匹配数, 然后对每个外籍飞行员dfs,若终点不是s且边权为0,则为一种搭配,输出就好了

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 250;
int INF = 2e9;

struct Edge
{
    int to, w, next;
} edge[maxn * maxn * maxn];

int n, m;

int k, head[maxn];
void add(int a, int b, int w){
    edge[k].to = b;
    edge[k].w = w;
    edge[k].next = head[a];
    head[a] = k++;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);
    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }

        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int cnt;
int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}
int main()
{
    scanf("%d%d", &m, &n);
    init();
    while(1){
        int a ,b;
        scanf("%d%d", &a, &b);
        if(a == -1 && b == -1){
            break;
        }
        add(a, b, 1);
        add(b, a, 0);
    }
    for(int i = 1; i <= m; i++){
        add(0, i, 1);
        add(i, 0, 0);
    }

    for(int i = 1; i <= n; i++){
        add(m + i, n + 1, 1);
        add(n + 1, m + i, 0);
    }
    int ans = Dinic(0, n + 1);
    if(ans == 0){
        printf("No Solution!\n");
        return 0;
    } else{
        printf("%d\n", ans);
    }
    for(int i = 1; i <= m; i++){
        for(int j = head[i]; j != -1; j = edge[j].next){
            int to = edge[j].to;
            if(to == 0){
                continue;
            }
            if(edge[j].w == 0){
                printf("%d %d\n", i, to);
            }
        }
    }
    return 0;
}

 

二、 P2762  太空飞行计划问题

思路:最大权闭合子图模板题,不懂什么是最大权闭合子图的可以去网上找博客,很多大佬都有写

          s向每个实验建边,边权为实验的经费的绝对值,所有实验和对应的器材建边,边权为INF(无穷大), 所有器材和t建边,边权为花费的绝对值,跑一边Dinic后,可以得出最小割,sum为所有实验经费的和,则最大权为sum - 最小割(其实就是最大流)。最小割将原图分为两个闭合子图,s所在的子图即使最大权闭合子图,所以我们可以从s开始dfs,权值为0的边不走,能走到的所有点则为要选的实验和器材,又因为最后一次bfs的到的dis数组就代表了求出最小割后,s所能到达的点,所以我们直接利用dis数组,输出dis数组中不为0的数就好了

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int maxn = 250;
int INF = 2e9;
int n, m;

struct Edge
{
    int to, next, w;
} edge[maxn * maxn];

int k = 0;
int head[maxn];

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);
    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }

        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }

    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }

    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }

    return ans;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

char tools[10000];
int main()
{
    init();
    scanf("%d%d", &m, &n);
    int sum = 0;
    for(int i = 1; i <= m; i++){
        int profit;
        scanf("%d", &profit);
        sum += profit;
        add(0, i, profit);
        add(i, 0, 0);
        memset(tools, 0, sizeof(tools));
        cin.getline(tools, 100000);
        int ulen = 0, tool;
        while(sscanf(tools + ulen, "%d", &tool) == 1){
            add(i, tool + m, INF);
            add(tool + m, i, 0);
            if(tool == 0){
                ulen++;
            } else{
                while(tool){
                    tool /= 10;
                    ulen++;
                }
            }
            ulen++;
        }
    }

    for(int i = 1; i <= n; i++){
        int x;
        scanf("%d", &x);
        add(m + i, n + m + 1, x);
        add(n + m + 1, m + i, 0);
    }

    int res = Dinic(0, n + m + 1);

    for(int i = 1; i <= m; i++){
        if(dis[i]){
            printf("%d ", i);
        }
    }
    printf("\n");
    for(int i = 1; i <= n; i++){
        if(dis[i + m]){
            printf("%d ", i);
        }
    }
    printf("\n");
    printf("%d\n", sum - res);
    return 0;
}

 

三、P2764 最小路径覆盖问题

思路:把每个点拆成前驱结点a和后继节点b, 如果存在一条边(u, v), 就将u的a和v的b相连,此处需要二分图匹配的知识,然后将s和每个a相连,每个b和t相连,正向边权值均为1,反向边为0,跑一次最大流,也就是最大匹配数,所有没有被匹配的b对应的点的入度为0,即为每条路的起点,求路径数最少等价于找到最少的起点,而最大匹配保证了入读为0的点最少,具体可以看这个大佬的博客https://blog.csdn.net/Tramp_1/article/details/52742572, 最后dfs找路径就好了

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int maxn = 400;
int INF = 2e9;
int n, m;

struct Edge
{
    int to, next, w;
} edge[maxn * maxn];

int k, head[maxn];

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);

    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }
        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            if(!dis[to] && edge[i].w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

vector vec;
int vis[maxn];
void Find(int u){
    if(u > n){ // 注意,我们每次都要从前驱节点去寻找,所以要将后继节点变为前去节点再输出
        u -= n;
    }
    printf("%d ", u);
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        if(edge[i].w == 0 && to != 0){
            Find(to);
        }
    }
}
int main()
{
    init();
    scanf("%d%d", &n, &m);
    for(int i = 0; i < m; i++){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b + n, 1); 
        add(b + n, a, 0);
    }

    for(int i = 1; i <= n; i++){
        add(0, i, 1);
        add(i, 0, 0);
        add(i + n, 2 * n + 1, 1);
        add(2 * n + 1, i + n, 0);
    }

    int res = Dinic(0, 2 * n + 1);
    for(int i = head[2 * n + 1]; i != -1; i = edge[i].next){ // 从t遍历每个后继节点
        int to = edge[i].to;
        if(edge[i].w == 0){ //若反向边的流量为0,说明该后继节点没有流向t,说明该节点未被匹配,则是一个起点
            vec.push_back(to);
        }
    }

    int len = vec.size();
    for(int i = 0; i < len; i++){
        Find(vec[i]);
        printf("\n");
    }

    printf("%d\n", n - res);
    return 0;
}

 

四、 P2765 魔术球问题

思路:还是一道最小路径覆盖,从1开始枚举每个编号,当枚举到x时,从1开始枚举到x - 1,设这个数为a,若(a + x) 时完全平方数,则把a的前驱结点想x的后继节点连一条权值为1的边,s向每个前驱结点连边,每个后继节点向t连边,权值均为1,反向边权值均为0,由于我们每次加边后是可以利用之前已经跑过的图上继续跑的,所以我们不用每次都重新建图,设Dinic的结果为res,则x - res为最小路径数也就是所需的最少的柱子,托x - res > n,则x - 1就是答案,然后再向第三题那样输出路径就好了,唯一有可能出问题的是数组可能会开小了,所以我直接把数组开的比较大。。。

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e5 + 50;
int INF = 2e9;

struct Edge
{
    int to, next, w; 
} edge[maxn * 100];

int head[maxn], k;

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}
int vis[maxn * 100]; // vis[i] = 1 表示i是完全平方数
int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);

    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }

        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}
int sum = 0;
int Dinic(int s, int t){
    
    while(bfs(s, t)){
        sum += dfs(s, INF, t);
    }
    return sum;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

void Find(int u){
    if(u > 2000){
        u -= 2000;
    }
    printf("%d ", u);
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(to != 0 && w == 0){
            Find(to);
        }
    }
}
int main()
{
    init();
    for(int i = 1; i <= 100; i++){
        vis[i * i] = 1;
    }
    int n;
    scanf("%d", &n);
    int s = 0, t = 5000;
    int ans = 0;
    while(++ans){
        add(s, ans, 1);
        add(ans, s, 0);
        add(ans + 2000, t, 1);
        add(t, ans + 2000, 0);
        for(int i = 1; i < ans; i++){
            if(vis[i + ans]){
                add(i, ans + 2000, 1);
                add(ans + 2000, i, 0);
            }
        }

        int res = Dinic(s, t);
        if(ans - res > n){
            break;
        }
    }

    printf("%d\n", ans - 1);
    for(int i = head[t]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        if(edge[i].w == 0 && to != ans + 2000){ //注意不能连到ans,因为ans是放不上去的
            Find(to);
            printf("\n");
        }
        
    }
    return 0;
}

 

五、P3254 圆桌问题

思路:二分图多重匹配模板题,不了解的可以看一下这个大佬的博客https://blog.csdn.net/Tramp_1/article/details/52663763

           s向每个代表团建一条权值为代表团人数的边,每个代表团向每个桌子连一条权值为1的边(保证每个桌子上相同代表团的只有一人),每个桌子向t连一条权值为桌子可坐人数的边,所有反边权值为0.跑一遍Dinic,若最大流和代表团人数相等,则说明有可行方案,然后一每个代表团为起点,若连向桌子的边权值为0,说明该代表团有人去该桌子,输出就好了

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e4;
int INF = 2e9;
int n, m;
struct Edge
{
    int to, next, w; 
} edge[maxn * 100];

int head[maxn], k;

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}
int dis[maxn];

int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    que.push(s);
    dis[s] = 1;
    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }
        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}
int main()
{
    init();
    scanf("%d%d", &m, &n);
    int sum = 0;
    int s = 0, t = n + m + 1;
    for(int i = 1; i <= m; i++){
        int x;
        scanf("%d", &x);
        sum += x;
        add(s, i, x);
        add(i, s, 0);
    }

    for(int i = 1; i <= n; i++){
        int x;
        scanf("%d", &x);
        add(m + i, t, x);
        add(t, m + i, 0);
    }

    for(int i = 1; i <= m; i++){
        for(int j = 1; j <= n; j++){
            add(i, m + j, 1);
            add(m + j, i, 0);
        }
    }

    int res = Dinic(s, t);
    if(res == sum){
        printf("1\n");
        for(int i = 1; i <= m; i++){
            for(int j = head[i]; j != -1; j = edge[j].next){
                int to = edge[j].to;
                int w = edge[j].w;
                if(w == 0 && to != s){
                    printf("%d ", to - m);
                }
            }
            printf("\n");
        }
    } else{
        printf("0\n");
    }
    return 0;
}

 

六、P2766 最长不下降子序列问题

思路:一道最多不相交路径问题,最长不降子序列用dp求解,dp[i]表示1到 i 能获得的最上不降子序列,

           第二问:由于每个点只能用一次,把每个点拆为a (入点)和a + n(出点), 可以明显的看出,点直接转移是有条件,设ss为原序列当(i < j &&  ss[i] <= ss[j] && dp[j]  == dp[i] + 1) 是点 i 才可以转移到 j 点,若i, j满足条件,则 i + n向 j 建边,权值为1,同时只有dp[i] = 1的点才可以作为起点,设ma为最大长度,只有dp[i] = ma的点才可以作为终点,s和所有起点建边,权值为1,所有终点和t建边,权值为1,所有反边权值为0,Dinic跑一下就是结果了

           第三问:由于点1和点n可以无限用,则把s到1的, 1 到 1 + n的, n到 2 * n的权值修改为INF(正无穷),如果dp[n] == ma,把2 * n到 t 的边的权值也改为INF,直接在之前的图上跑出来的结果就是答案了

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e4;
int INF = 2e9;
int n, m;
int ss[maxn];
int dp[maxn];
struct Edge
{
    int to, next, w; 
} edge[maxn * 100];

int head[maxn], k;

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);
    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }
        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int ans = 0; // 由于要跑两次,定义为全局变量
int Dinic(int s, int t){
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

int main()
{
    init();
    scanf("%d", &n);
    int s = 0, t = 2 * n + 1;
    for(int i = 1; i <= n; i++){
        add(i, i + n, 1);
        add(i + n, i, 0);
        scanf("%d", &ss[i]);
    }
    ss[0] = 0;
    int ma = 0;
    for(int i = 1; i <= n; i++){
        for(int j = 0; j < i; j++){
            if(ss[j] <= ss[i]){
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        ma = max(ma, dp[i]);
    }
    for(int i = 1; i <= n; i++){
        if(dp[i] == 1){
            add(s, i, 1);
            add(i, s, 0);
        }
        if(dp[i] == ma){
            add(i + n, t, 1);
            add(t, i + n, 0);
        }
        for(int j = i + 1; j <= n; j++){
            if(ss[i] <= ss[j] && dp[i] + 1 == dp[j]){
                add(i + n, j, 1);
                add(j, i + n, 0);
            }
        }
    }

    int res1 = Dinic(s, t);

    for(int i = head[s]; i != -1; i = edge[i].next){ // s 到 1
        int to = edge[i].to;
        if(to == 1){
            edge[i].w = INF;
            break;
        }
    }

    for(int i = head[1]; i != -1; i = edge[i].next){ // 1 到 1 + n
        int to = edge[i].to;
        if(to == 1 + n){
            edge[i].w = INF;
            break;
        }
    }

    for(int i = head[n]; i != -1; i = edge[i].next){ // n 到 2 * n
        int to = edge[i].to;
        if(to == 2 * n){
            edge[i].w = INF;
            break;
        }
    }

    if(dp[n] == ma){
        for(int i = head[n * 2]; i != -1; i = edge[i].next){ // 2 * n 到 t
            int to = edge[i].to;
            if(to == t){
                edge[i].w = INF;
                break;
            }
        }
    }

    int res2 = Dinic(s, t);
    printf("%d\n", ma);
    printf("%d\n", res1);
    printf("%d\n", res2);
    return 0;
}

 

七、P2763 试题库问题

思路:二分图匹配问题,由于每个试题只能用一次,我们需要把每个试题a拆成 a(入点) 和 a + n(出点) 两个点,a 向 a + n建立权值为1的边,每种类型b在图上对应的点的编号则为 2 * n + b,s像每个入点建立权值为1的边,每个出点与对应的试题类型建立权值为1的边,每个试题类型向t建立权值为对应的所需数量的边,所有反边权值为0,跑一遍Dinic,设最大流为res,设所需试题总数为sum, 若sum == res,则有解,那么以每个试题类型为起点,遍历所有与该试题类型为起点的所有边,若终点不是t,且权值为1,则输出对应的试题编号

#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e4;
int INF = 2e9;
int n, m;
int ss[maxn];
int dp[maxn];
struct Edge
{
    int to, next, w; 
} edge[maxn * 1000];

int head[maxn], k;

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

int dis[maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    que.push(s);
    dis[s] = 1;
    que.push(s);

    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }
        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}
void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

int main()
{
    init();
    scanf("%d%d", &m, &n);
    int s = 0, t = n * 2 + m + 1;
    int sum = 0;
    for(int i = 1; i <= m; i++){
        int x;
        scanf("%d", &x);
        sum += x;
        add(2 * n + i, t, x);
        add(t, 2 * n + i, 0);
    }

    for(int i = 1; i <= n; i++){
        add(s, i, 1);
        add(i, s, 0);
        add(i, i + n, 1);
        add(i + n, i, 0);
        int cnt;
        scanf("%d", &cnt);
        while(cnt--){
            int x;
            scanf("%d", &x);
            add(i + n, 2 * n + x, 1);
            add(2 * n + x, i + n, 0);
        }
    }

    int res = Dinic(s, t);
    if(res != sum){
        printf("No Solution\n");
        return 0;
    }

    for(int i = 1; i <= m; i++){
        printf("%d: ", i);
        for(int j = head[2 * n + i]; j != -1; j = edge[j].next){
            int to = edge[j].to;
            int w = edge[j].w;
            if(to == t){
                continue;
            }
            if(w == 1){
                printf("%d ", to - n); // 要减去n才是正确的编号
            }
        }
        printf("\n");
    }
    return 0;
}

 

八、P2774 方格取数问题

思路:最大独立点集,这种东西去百度一下博客就可以学会了,先将方格染成黑白两色,相邻方格颜色不同,坐标和为偶数的为白色,奇数为黑色,s向所有白色点连权值为对应方格数值的边,所有白色方格向相邻的黑色方格连权值为INF(无穷大)的边,注意:黑色不用向白色建立权值为INF的边,所有黑色向 t 建立权值为i对应方格数值的点, 跑一遍FDInic,用总权值减去最大流就是结果了

#include 
using namespace std;
const int maxn = 200;

int INF = 1e9;
int n, m;
int mp[maxn][maxn];
int tx[] = {0, -1, 0, 1};
int ty[] = {1, 0, -1, 0};
struct Edge
{
    int to, w, next;
} edge[maxn * maxn * maxn];

int k, head[maxn * maxn];

void add(int a, int b, int c){
    edge[k].to = b;
    edge[k].w = c;
    edge[k].next = head[a];
    head[a] = k++;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}


int dis[maxn * maxn];
int bfs(int s, int t){
    queue que;
    memset(dis, 0, sizeof(dis));
    dis[s] = 1;
    que.push(s);
    while(que.size()){
        int u = que.front();
        que.pop();
        if(u == t){
            return 1;
        }
        for(int i = head[u]; i != -1; i = edge[i].next){
            int to = edge[i].to;
            int w = edge[i].w;
            if(!dis[to] && w){
                dis[to] = dis[u] + 1;
                que.push(to);
            }
        }
    }
    return 0;
}

int dfs(int u, int maxf, int t){
    if(u == t){
        return maxf;
    }
    int ret = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].w;
        if(dis[to] == dis[u] + 1 && w){
            int mi = min(maxf - ret, w);
            w = dfs(to, mi, t);
            edge[i].w -= w;
            edge[i ^ 1].w += w;
            ret += w;
            if(ret == maxf){
                return ret;
            }            
        }
    }
    return ret;
}

int Dinic(int s, int t){
    int ans = 0;
    while(bfs(s, t)){
        ans += dfs(s, INF, t);
    }
    return ans;
}

int main(int argc, char const *argv[])
{
    init();
    scanf("%d%d", &n, &m);
    int sum = 0;
    int s = 0, t = n * m + 1;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            scanf("%d", &mp[i][j]);
            sum += mp[i][j];
        }
    }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            int id = (i - 1) * m + j;
            if((i + j) % 2 == 0){
                add(s, id, mp[i][j]);
                add(id, s, 0);
            } else{
                add(id, t, mp[i][j]);
                add(t, id, 0);
                continue; // 我们要从坐标和为偶数的点向奇数连边
            }

            for(int k = 0; k < 4; k++){
                int nx = i + tx[k];
                int ny = j + ty[k];
                if(nx >= 1 && nx <= n && ny >= 1 && ny <= m){
                    int id2 = (nx - 1) * m + ny;
                    add(id, id2, INF);
                    add(id2, id, 0);
                }
            }
        }
    }

    printf("%d\n", sum - Dinic(s, t));
    return 0;
}

九、P1251 餐巾计划问题

最小费用流的题目,emmm,这题的建图就和奇妙,我自己建了好几个图,都不能解决把一天使用的数量的餐巾流向t又流向其他时间的问题(会分流,那么t就不能接受一天内用过的餐巾了),后来看了别人的博客。。。。这篇讲的不错

https://www.cnblogs.com/bztMinamoto/p/9507521.html

#include 
using namespace std;

#define N 10000
typedef long long LL;
typedef pair P;
LL INF = 1e18;

struct Edge
{
    int to;
    LL cap, cost;
    int rev;
    Edge(int t, LL c, LL cc, int r) : to(t), cap(c), cost(cc), rev(r){}
};
LL num[N];
int V;
vector G[N];
LL h[N];
LL dist[N];
int prevv[N];
int preve[N];

void addedge(int from, int to, LL cap ,LL cost){
    G[from].push_back(Edge(to, cap, cost, G[to].size()));
    G[to].push_back(Edge(from, 0, -cost, G[from].size() - 1));
}

LL min_cost_flow(int s, int t, LL f){
    LL res = 0;
    fill(h, h + V, 0);
    while(f > 0){
        priority_queue, greater

> que; fill(dist, dist + V, INF); dist[s] = 0; que.push(P(0, s)); while(!que.empty()){ P p = que.top(); que.pop(); int v = p.second; if(dist[v] < p.first){ continue; } int len = G[v].size(); for(int i = 0; i < len; i++){ Edge &e = G[v][i]; if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]){ dist[e.to] = dist[v] + e.cost + h[v] - h[e.to]; prevv[e.to] = v; preve[e.to] = i; que.push(P(dist[e.to], e.to)); } } } if(dist[t] == INF){ return -1; } for(int j = 0; j < V; j++){ h[j] += dist[j]; } LL d = f; for(int x = t; x != s; x = prevv[x]){ d = min(d, G[prevv[x]][preve[x]].cap); } f -= d; res += d * h[t]; for(int x = t; x != s; x = prevv[x]){ Edge &e = G[prevv[x]][preve[x]]; e.cap -= d; G[x][e.rev].cap += d; } } return res; } int main() { LL n; LL p, p1, p2; LL t1, t2; scanf("%lld", &n); LL sum = 0; LL s = 0, t = n * 2 + 1; V = t + 1; for(LL i = 1; i <= n; i++){ scanf("%lld", &num[i]); sum += num[i]; } scanf("%lld%lld%lld%lld%lld", &p, &t1, &p1, &t2, &p2); for(LL i = 1; i <= n; i++){ addedge(s, i, num[i], 0); addedge(i + n, t, num[i], 0); addedge(s, i + n, INF, p); if(i != n){ addedge(i, i + 1, INF, 0); } if(i + t1 <= n){ addedge(i, i + t1 + n, INF, p1); } if(i + t2 <= n){ addedge(i, i + t2 + n, INF, p2); } } printf("%lld\n", min_cost_flow(s, t, sum)); return 0; }

十、P2770 航空路线问题

最长不相交路径,可转化为最小费用流模型,因为每个点只能走一次,所以把每个点拆成两个点,入点a和出点b,这题里的 s 就是第一个城市, t 就是最后一个城市, 若存在边u, v ,则把u的b连向v的a,容量为1,费用为-1, 反边费用为1,因为我们要求的是最长路,所以正向边费用不能为1,除了点1和点n的入点到处点的容量为2,其他的入点到出点的容量均为1,费用为0。之后就是跑一遍最小费用流,若流量能达到2,则输出路径(具体看代码),若流量达不到2,并且不存在1到n的路径,输出“No Solution!”, 否则输出1, n, 1.。。。

#include 
using namespace std;
const int maxn = 300;
int INF = 1e9;
int n, m;
string ss[200];
map mmap;
struct Edge
{
    int to, next, cap, cost;
} edge[maxn * maxn];

int k, head[maxn];

void add(int a, int b, int w, int c){
    edge[k].to = b;
    edge[k].cap = w;
    edge[k].cost = c;
    edge[k].next = head[a];
    head[a] = k++;
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}
int dis[maxn];
int vis[maxn];
int flow[maxn];
int pre[maxn];
int res;
int ek(int s, int t, int f){
    while(f > 0){
        for(int i = 0; i <= 2 * n + 1; i++){
            dis[i] = INF;
            vis[i] = 0;
            pre[i] = 0;
            flow[i] = INF;
        }
        dis[s] = 0;
        queue que;
        que.push(s);
        vis[s] = 1;
        flow[s] = INF;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to = edge[i].to;
                int w = edge[i].cap;
                int c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    flow[to] = min(flow[u], w);
                    dis[to] = dis[u] + c;
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            }
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i ; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f -= flow[t];
        res += dis[t] * flow[t];
    }
    if(f == 0){
        return 1;
    } else{
        return 0;
    }
}

void Find1(int u){
    if(u == n){
        return ;
    }
    if(u <= n){
        cout << ss[u] << endl;
    }
    vis[u] = 1;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].cap;
        if(w == 0 && vis[to] == 0 && i % 2 == 0){ //这里走的是正边
            Find1(to);
            break;
        }
    }
}
void Find2(int u){
    if(u <= n){
        cout << ss[u] << endl;
    }
    vis[u] = 1;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int to = edge[i].to;
        int w = edge[i].cap;
        if(w && vis[to] == 0 && i % 2 == 1){ //这里走的是反边
            Find2(to);
        }
    }
}
int main(int argc, char const *argv[])
{
    init();
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        string s1;
        cin >> s1;
        ss[i] = s1;
        
        mmap[s1] = i;
        add(i, i + n, 1, 0);
        add(i + n, i, 0, 0);
    }

    add(1, 1 + n, 1, 0);
    add(1 + n, 1, 0, 0);
    add(n, n * 2, 1, 0);
    add(n * 2, n, 0, 0);
    int flag = 0;
    for(int i = 0; i < m; i++){
        string s1, s2;
        cin >> s1 >> s2;
        int id1 = mmap[s1];
        int id2 = mmap[s2];
        if(id1 > id2){
            swap(id1, id2);
        }
        if(id1 == 1 && id2 == n){
            flag = 1;
        }
        add(id1 + n, id2, 1, -1);
        add(id2, id1 + n, 0, 1);
    }
    if(!ek(1, 2 * n, 2)){
        if(flag){
            cout << 2 << endl;
            cout << ss[1] << endl;
            cout << ss[n] << endl;
            cout << ss[1] << endl;
        } else{
            printf("No Solution!\n");
        }
        return 0;
    }
    memset(vis, 0, sizeof(vis));
    printf("%d\n", -res);
    Find1(1);
    Find2(n);
    cout << ss[1] << endl;
    return 0;
}

 

十一、P4011 孤岛营救问题

思路:因为只有10把钥匙,所以我们可以状压钥匙的状态,然后暴力过就好了,可以用优先队列的dj优化(这里是优化的版本

#include 
using namespace std;
const int maxn = 15;
int INF = 0x3f3f3f3f;
int have[maxn][maxn][maxn][maxn];
int dp[maxn][maxn][(1 << 14) + 50];
int mp[maxn][maxn];
int n, m, p;

int tx[] = {0, -1, 0, 1};
int ty[] = {1, 0, -1, 0};
struct qnode
{
    int x, y;
    int ke;
    int c;
    bool operator < (const qnode & r) const{
        return c > r.c;
    }
};

priority_queue que;
int bfs(){
    memset(dp, INF, sizeof(dp));
    int ke = 0;
    if(mp[1][1]){
        ke |= mp[1][1];
    }
    que.push(qnode{1, 1, ke, 0});
    dp[1][1][ke] = 0;
    while(que.size()){
        qnode tmp = que.top();
        que.pop();
        int x = tmp.x;
        int y = tmp.y;
        
        ke = tmp.ke;
        //printf("x = %d y = %d ke = %d\n", x, y, ke);
        if(x == n && y == m){
            return tmp.c;
        }
        for(int i = 0; i < 4; i++){
            int nx = x + tx[i];
            int ny = y + ty[i];
            int t = have[x][y][nx][ny];
            if(t == 0){
                continue;
            }
            if(t > 0 && ((1 << (t - 1)) & ke) == 0){
                continue;
            }
            if(nx >= 1 && nx <= n && ny >= 1 && ny <= m && dp[nx][ny][ke] > dp[x][y][ke] + 1){
                dp[nx][ny][ke] = dp[x][y][ke] + 1;
                int ke2 = ke;
                if(mp[nx][ny]){
                    ke2 |= mp[nx][ny];
                    dp[nx][ny][ke2] = dp[nx][ny][ke];
                    que.push(qnode{nx, ny, ke2, dp[nx][ny][ke2]});
                }
                
                que.push(qnode{nx, ny, ke, dp[nx][ny][ke]});
                
                //printf("999\n");
            }
        }
    }
    return -1;
}
int main(int argc, char const *argv[])
{
    memset(have, -1, sizeof(have));
    scanf("%d%d%d", &n, &m, &p);
    int k;
    scanf("%d", &k);
    for(int i = 1; i <= k; i++){
        int a1, b1, a2, b2, c;
        scanf("%d%d%d%d%d", &a1, &b1, &a2, &b2, &c);
        have[a1][b1][a2][b2] = c;
        have[a2][b2][a1][b1] = c;
    }
    int s;
    scanf("%d", &s);
    for(int i = 0; i < s; i++){
        int x, y, c;
        scanf("%d%d%d", &x, &y, &c);
        mp[x][y] |= (1 << (c - 1));
    }
    int xx[10][10];
    memset(xx, INF, sizeof(xx));
    printf("%d\n", bfs());
    return 0;
}

 

十二、P4013 数字梯形问题

最大流最大费用板子题, 三种情况

第一问:每个点只能用一次,祖传拆点,然后点之间的建容量为1, 边权为该点权值的相反数的边(因为要求最大费用)(其实这个容量是随意的,因为点不相同的话,一定不会经过相同的边,但我们第二问的时候要保证点之间容量为1,所以我们就这么建) ,跑一遍最大流最大费用就是结果了。

第二问:这里我们是前向星存图,我们建边的是用一个ca来保存这条边原本的cap(容量), 第一问跑完后遍历所有边,

让cap = ca, 这样就恢复成最初的图,不用重新建图,很方便,这里我们要在每对拆点之间加上一条容量为INF,权值为该点值的相反数的边,而之前我们就已经把点之间的流量设为1了,所以这里可以直接用

第三问:还是先恢复所有边的容量,然后在点之间加上一条容量为INF,边权为0的边就好了

注意:第二问的时候就要把每条连向 t 的容量设为INF

#include 
using namespace std;

const int maxn = 2e6 + 60;
int INF = 1e9;
int dis[maxn], vis[maxn], flow[maxn], pre[maxn];
int n;
struct Edge
{
    int ca;
    int to, next, cap, cost;    
} edge[maxn * 2];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
    edge[k].to = b;

    edge[k].cap = cap;
    edge[k].cost = cost;
    edge[k].next = head[a];
    edge[k].ca = cap;
    head[a] = k++;

    edge[k].to = a;
    edge[k].cap = 0;
    edge[k].cost = -cost;
    edge[k].next = head[b];
    edge[k].ca = 0;
    head[b] = k++;
}
int res;
int ins;

int ek(int s, int t, int f){
    while(f > 0){
        for(int i = 0; i <= ins * 2 + 1; i++){
            dis[i] = INF;
            vis[i] = 0;
            pre[i] = -1;
        }
        dis[s] = 0;
        queue que;
        que.push(s);
        vis[s] = 1;
        flow[s] = INF;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to = edge[i].to;
                int w = edge[i].cap;
                int c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    flow[to] = min(flow[u], w);
                    dis[to] = dis[u] + c;
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            } 
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i != -1 ; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f -= flow[t];
        res += dis[t] * flow[t];
    }

    if(f == 0){
        return 1;
    } else{
        return 0;
    }
}

int tri[2000][2000];
int id[2000][2000];
int main(int argc, char const *argv[])
{
    k = 0;
    memset(head, -1, sizeof(head));
    int m, n;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m + i - 1; j++){
            id[i][j] = ++ins;
            scanf("%d", &tri[i][j]);
        }
    }
    int s = 0, tt = 2 * ins + 1;
    for(int i = 1; i <= m; i++){
        add(s, id[1][i], 1, 0);
    }
    for(int i = 1; i < n; i++){
        for(int j = 1; j <= m + i - 1; j++){
            add(id[i][j] + ins, id[i + 1][j], 1, 0);
            add(id[i][j] + ins, id[i + 1][j + 1], 1, 0);
        }
    }
    for(int i = 1; i <= m + n - 1; i++){
        add(id[n][i] + ins, tt, 1, 0);
    }


    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m + i - 1; j++){
            add(id[i][j], id[i][j] + ins, 1, -tri[i][j]);

        }
    }
    ek(s, tt, m);
    printf("%d\n", -res);

    for(int i = 0; i < k; i++){
        edge[i].cap = edge[i].ca;
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m + i - 1; j++){
            add(id[i][j], id[i][j] + ins, INF, -tri[i][j]);
        }
    }
    for(int i = 1; i <= m + n - 1; i++){
        add(id[n][i] + ins, tt, INF, 0);
    }
    res = 0;
    ek(s, tt, m);
    printf("%d\n", -res);
    for(int i = 0; i < k; i++){ // 恢复容量
        edge[i].cap = edge[i].ca;
    }
    for(int i = 1; i < n; i++){
        for(int j = 1; j <= m + i - 1; j++){
            add(id[i][j] + ins, id[i + 1][j], INF, 0);
            add(id[i][j] + ins, id[i + 1][j + 1], INF, 0);
        }
    }
    res = 0;
    
    ek(s, tt, m);
    printf("%d\n", -res);
    return 0;
}

 

十三、P4015 运输问题 

思路: 最小费用流模板题,后面求最大费用直接把容量恢复,并且把每条边的花费变为相反数就好了

#include 
using namespace std;

const int maxn = 1e5 + 50;
int INF = 1e9;
int n, m;
struct Edge
{
    int to, next, ca, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
    edge[k].to = b;
    edge[k].next = head[a];
    edge[k].ca = cap;
    edge[k].cap = cap;
    edge[k].cost = cost;
    head[a] = k++;

    edge[k].to = a;
    edge[k].cap = 0;
    edge[k].ca = 0;
    edge[k].cost = -cost;
    edge[k].next = head[b];
    head[b] = k++;
}

int dis[maxn], vis[maxn], flow[maxn], pre[maxn];
int res = 0;
int ek(int s, int t, int f){
    //printf("s = %d t = %d f = %d\n", s, t, f);
    while(f > 0){
        for(int i = 0; i <= t; i++){
            dis[i] = INF, vis[i] = 0, pre[i] = -1, flow[i] = INF;
        }
        dis[s] = 0;
        queue que;
        que.push(s);
        vis[s] = 1;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                
                int to = edge[i].to;
                int w = edge[i].cap;
                int c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    
                    flow[to] = min(flow[u], w);
                    dis[to] = dis[u] + c;
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            }
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f -= flow[t];
        res += dis[t] * flow[t];
    }

    if(f == 0){
        return 1;
    } else{
        return 0;
    }
}

void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
    scanf("%d%d", &n, &m);
    init();
    int sum = 0;
    int s = 0, t = n + m + 1;
    for(int i = 1; i <= n; i++){
        int x;
        scanf("%d", &x);
        sum += x;
        add(s, i, x, 0);
    }

    for(int i = 1; i <= m; i++){
        int x;
        scanf("%d", &x);
        add(n + i, t, x, 0);
    }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            int x;
            scanf("%d", &x);
            add(i, n + j, INF, x);
        }
    }
    ek(s, t, sum);
    printf("%d\n", res);
    for(int i = 0; i < k; i++){
        edge[i].cap = edge[i].ca;
        edge[i].cost = -edge[i].cost;
    }
    res = 0;
    ek(s, t, sum);
    printf("%d\n", -res);
    return 0;
}

 

十四、P4014 分配问题

思路:和上面的十三一个写法。。。就不解释了

#include 
using namespace std;

const int maxn = 1e5 + 50;
int INF = 1e9;
int n, m;
struct Edge
{
    int to, next, ca, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
    edge[k].to = b;
    edge[k].cap = cap;
    edge[k].ca = cap;
    edge[k].cost = cost;
    edge[k].next = head[a];
    head[a] = k++;

    edge[k].to = a;
    edge[k].cap = 0;
    edge[k].ca = 0;
    edge[k].cost = -cost;
    edge[k].next = head[b];
    head[b] = k++;
}

int dis[maxn], flow[maxn], vis[maxn], pre[maxn];
int res = 0;
int ek(int s, int t, int f){
    while(f > 0){
        for(int i = 0; i <= t; i++){
            dis[i] = INF, vis[i] = 0, pre[i] = -1, flow[i] = INF;
        }
        dis[s] = 0;
        queue que;
        que.push(s);
        vis[s] = 1;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to = edge[i].to;
                int w = edge[i].cap;
                int c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    flow[to] = min(flow[u], w);
                    dis[to] = dis[u] + c;
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            }
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f -= flow[t];
        res += dis[t] * flow[t];
    }
    if(f == 0){
        return 1;
    } else{
        return 0;
    }
}
void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
    init();
    scanf("%d", &n);
    int s = 0, t = 3 * n + 1;
    for(int i = 1; i <= n; i++){
        add(s, i, 1, 0);
        add(i + n, t, 1, 0);
        for(int j = 1; j <= n; j++){
            int x;
            scanf("%d", &x);
            add(i, j + n, 1, x);
        }
    }

    ek(s, t, n);
    printf("%d\n", res);

    for(int i = 0; i < k; i++){
        edge[i].cap = edge[i].ca;
        edge[i].cost *= -1;
    }
    res = 0;
    ek(s, t, n);
    printf("%d\n", -res);
    return 0;
}

 

十五、P4016 负载平衡问题

思路:最小费用流,s向每个仓库连容量为对应数量,花费为0的边,每个仓库向相邻的仓库建容量为INF, 权值为1的边,每个仓库向t建立容量为sum / n,费用为0的边,跑一遍费用流就好了

#include 
using namespace std;

const int maxn = 1e5 + 50;
int INF = 1e9;
int n, m;
int num[maxn];
struct Edge
{
    int to, next, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
    edge[k].to = b;
    edge[k].cap = cap;
    edge[k].cost = cost;
    edge[k].next = head[a];
    head[a] = k++;

    edge[k].to = a;
    edge[k].cap = 0;
    edge[k].cost = -cost;
    edge[k].next = head[b];
    head[b] = k++;
}

int dis[maxn], flow[maxn], vis[maxn], pre[maxn];
int res;
int ek(int s, int t, int f){
    res = 0;
    while(f > 0){
        for(int i = 0; i <= t; i++){
            dis[i] = flow[i] = INF;
            pre[i] = -1;
            vis[i] = 0;
        }

        queue que;
        que.push(s);
        dis[s] = 0;
        vis[s] = 1;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to = edge[i].to, w = edge[i].cap, c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    dis[to] = dis[u] + c;
                    flow[to] = min(flow[u], w);
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            }
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f -= flow[t];
        res += dis[t] * flow[t];
    }
    if(f == 0){
        return 1;
    } else{
        return 0;
    }
}
void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
    init();
    scanf("%d", &n);
    int s = 0, t = n + 1;
    int sum = 0;
    for(int i = 1; i <= n; i++){
        scanf("%d", &num[i]);
        sum += num[i];
        add(s, i, num[i], 0);
    }

    add(1, 2, INF, 1);
    add(1, n, INF, 1);
    add(n, 1, INF, 1);
    add(n, n - 1, INF, 1);
    add(1, t, sum / n, 0);
    add(n, t, sum / n, 0);
    for(int i = 2; i < n; i++){
        add(i, i + 1, INF, 1);
        add(i, i - 1, INF, 1);
        add(i, t, sum / n, 0);
    }

    ek(s, t, sum);
    printf("%d\n", res);
    return 0;
}

 

十六、P4012 深海机器人问题

思路:最大流最大费用模板题,每两个点之间建一条费用为对应价值的相反数,容量为1的边,在建一条费用为0, 容量为iINF的边,s向每个起点建立容量为对应数量,费用为0的边,终点同理,然后判一遍最大流最大费用就好了

#include 
using namespace std;

const int maxn = 1e5 + 50;
int INF = 1e9;
int n, m;
int num[maxn];
struct Edge
{
    int to, next, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
    edge[k].to = b;
    edge[k].cap = cap;
    edge[k].cost = cost;
    edge[k].next = head[a];
    head[a] = k++;

    edge[k].to = a;
    edge[k].cap = 0;
    edge[k].cost = -cost;
    edge[k].next = head[b];
    head[b] = k++;
}

int dis[maxn], flow[maxn], vis[maxn], pre[maxn];
int res;
int f;
int ek(int s, int t){
    res = 0;
    f = 0;
    while(1){
        for(int i = 0; i <= t; i++){
            dis[i] = flow[i] = INF;
            pre[i] = -1;
            vis[i] = 0;
        }

        queue que;
        que.push(s);
        dis[s] = 0;
        vis[s] = 1;
        while(que.size()){
            int u = que.front();
            que.pop();
            vis[u] = 0;
            for(int i = head[u]; i != -1; i = edge[i].next){
                int to = edge[i].to, w = edge[i].cap, c = edge[i].cost;
                if(w && dis[to] > dis[u] + c){
                    dis[to] = dis[u] + c;
                    flow[to] = min(flow[u], w);
                    pre[to] = i;
                    if(!vis[to]){
                        que.push(to);
                        vis[to] = 1;
                    }
                }
            }
        }
        if(dis[t] == INF){
            break;
        }
        for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
            edge[i].cap -= flow[t];
            edge[i ^ 1].cap += flow[t];
        }
        f += flow[t];
        res += dis[t] * flow[t];
    }
    return f;
}
void init(){
    k = 0;
    memset(head, -1, sizeof(head));
}

int mp1[20][20], mp2[20][20];
int main(int argc, char const *argv[])
{
    init();
    int a, b;
    scanf("%d%d", &a, &b);
    scanf("%d%d", &n, &m);
    int s = 0, t = (n + 1) * (m + 1) + 1;
    for(int i = 0; i <= n; i++){
    	for(int j = 1; j <= m; j++){
    		scanf("%d", &mp1[i][j]);
    		add(i * (m + 1) + j, i * (m + 1) + j + 1, 1, -mp1[i][j]);
    		add(i * (m + 1) + j, i * (m + 1) + j + 1, INF, 0);
    	}
    }

    for(int j = 0; j <= m; j++){
    	for(int i = 1; i <= n; i++){
    		scanf("%d", &mp2[i][j]);
    		add((i - 1) * (m + 1) + j + 1, i * (m + 1) + j + 1, 1, -mp2[i][j]);
    		add((i - 1) * (m + 1) + j + 1, i * (m + 1) + j + 1, INF, 0);
    	}
    }

    for(int i = 0; i < a; i++){
    	int kk, r, c;
    	scanf("%d%d%d", &kk, &r, &c);
    	add(s, r * (m + 1) + c + 1, kk, 0);
    }

    for(int i = 0; i < b; i++){
    	int kk, r, c;
    	scanf("%d%d%d", &kk, &r, &c);
    	add(r * (m + 1) + c + 1, t, kk, 0);
    }

    ek(s, t);

    printf("%d\n", -res);
    return 0;
}

 

十七、P3357 最长k可重线段集问题

思路:最大流最大费用,emmm,需要离散化和拆点,不拆点若线段的两个x点相同的话会出现负环,s向点1连一条容量为k,费用为0的点,每个点拆除的两个点直接和每两个不同的点之间连容量为INF(出点和入点这里就不细说了,上面都说过),费用为0的点,最后一个点向 t 连容量为k,费用为0的边,然后考虑每条线段,若x点能不同,则连一条容量为1,费用为长度的相反数的边,相同则在该点拆出的两个点之间连一条容量为1,费用为长度的相反数的边,跑一边最大流最大费用就好了。

注意:这题会爆int,所以要开long long, 我比较蓝,所以写成int后一键替换成long long了,所以我的变量类型都是LL

#include 
using namespace std;
typedef long long LL;
const LL maxn = 1e5 + 50;
LL INF = 1e9;


struct Edge
{
	LL to, next, cap, cost;
} edge[maxn];

LL k, head[maxn];

void add(LL a, LL b, LL cap, LL cost){
	edge[k].to = b;
	edge[k].cap = cap;
	edge[k].cost = cost;
	edge[k].next = head[a];
	head[a] = k++;

	edge[k].to = a;
	edge[k].cap = 0;
	edge[k].cost = -cost;
	edge[k].next = head[b];
	head[b] = k++;
}

LL dis[maxn], vis[maxn], flow[maxn], pre[maxn];
LL res = 0;
LL ek(LL s, LL t, LL f){
	while(f > 0){
		for(LL i = 0; i <= t; i++){
			dis[i] = flow[i] = INF;
			pre[i] = -1;
			vis[i] = 0;
		}
		dis[s] = 0;
		queue que;
		que.push(s);
		vis[s] = 1;
		while(que.size()){
			LL u = que.front();
			que.pop();
			vis[u] = 0;
			for(LL i = head[u]; i != -1; i = edge[i].next){
				LL to = edge[i].to, w = edge[i].cap, c = edge[i].cost;
				if(w && dis[to] > dis[u] + c){
					flow[to] = min(flow[u], w);
					dis[to] = dis[u] + c;
					pre[to] = i;
					if(!vis[to]){
						que.push(to);
						vis[to] = 1;
					}
				}
			}
		}
		if(dis[t] == INF){
			break;
		}
		for(LL i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
			edge[i].cap -= flow[t];
			edge[i ^ 1].cap += flow[t];
		}
		f -= flow[t];
		res += dis[t] * flow[t];
	}
	if(f == 0){
		return 1; 
	} else{
		return 0;
	}
}

LL lsh[maxn];
struct date
{
	LL a1, b1, a2, b2;
	LL len;
} node[maxn];

LL cal(LL a1, LL b1, LL a2, LL b2){
	return sqrt((a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2));
}

void init(){
	k = 0;
	memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
	init();
	LL n, m;
	scanf("%lld%lld", &n, &m);
	LL cnt = 0;
	for(LL i = 1; i <= n; i++){
		scanf("%lld%lld%lld%lld", &node[i].a1, &node[i].b1, &node[i].a2, &node[i].b2);
		lsh[++cnt] = node[i].a1;
		lsh[++cnt] = node[i].a2;
		node[i].len = cal(node[i].a1, node[i].b1, node[i].a2, node[i].b2);
	}
	sort(lsh + 1, lsh + cnt + 1);
	cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
	LL s = 0, t = 2 * cnt + 1;
	for(LL i = 1; i <= n; i++){
		node[i].a1 = lower_bound(lsh + 1, lsh + cnt + 1, node[i].a1) - lsh;
		node[i].a2 = lower_bound(lsh + 1, lsh + cnt + 1, node[i].a2) - lsh;
		if(node[i].a1 == node[i].a2){
			add(node[i].a1, node[i].a1 + cnt, 1, -node[i].len);
		} else{
			add(node[i].a1 + cnt, node[i].a2, 1, -node[i].len);
		}
	}
	for(LL i = 1; i <= cnt; i++){
		add(i, i + cnt, INF, 0);
	}
	for(LL i = 1; i < cnt; i++){
		add(i + cnt, i + 1, INF, 0);
	}
	add(s, 1, m, 0);
	add(cnt + cnt, t, m, 0);
	ek(s, t, m);
	printf("%lld\n", -res);
    return 0;
}

十八、P3358 最长k可重区间集问题

思路:和上面那题一样的。。。还要简单点,这题恶心的是给的区间有L > R的情况。。。

#include 
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 50;
int INF = 1e9;


struct Edge
{
	int to, next, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
	edge[k].to = b;
	edge[k].cap = cap;
	edge[k].cost = cost;
	edge[k].next = head[a];
	head[a] = k++;

	edge[k].to = a;
	edge[k].cap = 0;
	edge[k].cost = -cost;
	edge[k].next = head[b];
	head[b] = k++;
}

int dis[maxn], vis[maxn], flow[maxn], pre[maxn];
int res = 0;
int ek(int s, int t, int f){
	while(f > 0){
		for(int i = 0; i <= t; i++){
			dis[i] = flow[i] = INF;
			pre[i] = -1;
			vis[i] = 0;
		}
		dis[s] = 0;
		queue que;
		que.push(s);
		vis[s] = 1;
		while(que.size()){
			int u = que.front();
			que.pop();
			vis[u] = 0;
			for(int i = head[u]; i != -1; i = edge[i].next){
				int to = edge[i].to, w = edge[i].cap, c = edge[i].cost;
				if(w && dis[to] > dis[u] + c){
					flow[to] = min(flow[u], w);
					dis[to] = dis[u] + c;
					pre[to] = i;
					if(!vis[to]){
						que.push(to);
						vis[to] = 1;
					}
				}
			}
		}
		if(dis[t] == INF){
			break;
		}
		for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
			edge[i].cap -= flow[t];
			edge[i ^ 1].cap += flow[t];
		}
		f -= flow[t];
		res += dis[t] * flow[t];
	}
	if(f == 0){
		return 1; 
	} else{
		return 0;
	}
}

int lsh[maxn];
struct date
{
	int a1, a2;
	int len;
} node[maxn];


void init(){
	k = 0;
	memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
	init();
	int n, m;
	scanf("%d%d", &n, &m);
	int cnt = 0;
	for(int i = 1; i <= n; i++){
		scanf("%d%d", &node[i].a1, &node[i].a2);
		lsh[++cnt] = node[i].a1;
		lsh[++cnt] = node[i].a2;
		if(node[i].a1 > node[i].a2){
			swap(node[i].a1, node[i].a2);
		}
		node[i].len = abs(node[i].a1 - node[i].a2);
	}
	sort(lsh + 1, lsh + cnt + 1);
	cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;
	int s = 0, t = 2 * cnt + 1;
	for(int i = 1; i <= n; i++){
		node[i].a1 = lower_bound(lsh + 1, lsh + cnt + 1, node[i].a1) - lsh;
		node[i].a2 = lower_bound(lsh + 1, lsh + cnt + 1, node[i].a2) - lsh;
		if(node[i].a1 == node[i].a2){
			add(node[i].a1, node[i].a1 + cnt, 1, -node[i].len);
		} else{
			add(node[i].a1 + cnt, node[i].a2, 1, -node[i].len);
		}
	}
	for(int i = 1; i <= cnt; i++){
		add(i, i + cnt, INF, 0);
	}
	for(int i = 1; i < cnt; i++){
		add(i + cnt, i + 1, INF, 0);
	}
	add(s, 1, m, 0);
	add(cnt + cnt, t, m, 0);
	ek(s, t, m);
	printf("%d\n", -res);
    return 0;
}

 

十九、P3356 火星探险问题

思路:最大流最大费用 + 记录路径,祖传拆点,除了是障碍物的点,出点和入点之间都建一条容量为INF,费用为0的边,若该点是石块,则再建一条容量为1,费用为-1的边,每对可到达的点之间建立一条容量INF,费用为0的边,跑一遍最大流最大费用。

这些都不难,难的是记录路径,我们每次从s开始dfs每次转移时,若当前点的是出点,则输出一次,具体请看代码

#include 
using namespace std;
typedef long long LL;
const int maxn = 1e6 + 50;
int INF = 1e9;

struct Edge
{
	int to, next, cap, cost;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int cap, int cost){
	edge[k].to = b;
	edge[k].cap = cap;
	edge[k].cost = cost;
	edge[k].next = head[a];
	head[a] = k++;

	edge[k].to = a;
	edge[k].cap = 0;
	edge[k].cost = -cost;
	edge[k].next = head[b];
	head[b] = k++;
}

int dis[maxn], vis[maxn], pre[maxn], flow[maxn];
int res = 0;
int f = 0;
int ek(int s, int t){
	res = 0;
	while(1){
		for(int i = 0; i <= t; i++){
			vis[i] = 0, pre[i] = -1, dis[i] = INF;
		}
		queue que;
		que.push(s);
		dis[s] = 0;
		flow[s] = INF;
		vis[s] = 1;
		while(que.size()){
			
			int u = que.front();
			que.pop();
			vis[u] = 0;
			for(int i = head[u]; i != -1; i = edge[i].next){
				int to = edge[i].to;
				int w = edge[i].cap;
				int c = edge[i].cost;
				if(w && dis[to] > dis[u] + c){
					flow[to] = min(flow[u], w);
					dis[to] = dis[u] + c;
					pre[to] = i;
					if(!vis[to]){
						vis[to] = 1;
						que.push(to);
					}
				}
			}
		}

		if(dis[t] == INF){
			break;
		}

		for(int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]){
			edge[i].cap -= flow[t];
			edge[i ^ 1].cap += flow[t];
		}
		f += flow[t];
		res += flow[t] * dis[t];
	}

	return res;
}

void init(){
	k = 0;
	memset(head, -1, sizeof(head));
}

int mp[50][50];
int visedge[maxn];

int id = 0;
int cnt;
void dfs(int u, int pre){
	if(u == cnt){ //走到终点就可以结束了
		return ;
	}
	for(int i = head[u]; i != -1; i = edge[i].next){
		int to = edge[i].to;
		int w = edge[i ^ 1].cap; //反边容量
		if(to == pre){
			continue;
		}
		if(w){ // 反边容量不为0说明这条边可走
			edge[i ^ 1].cap--; //走过后减一
			if(u > cnt){ // 如果u是出点
				if(u - cnt == to - 1){
					printf("%d %d\n", id, 1);
				} else{
					printf("%d %d\n", id, 0);
				}
			}
			dfs(to, u);
			break; //一定要break;
		}
	}
}

int main(int argc, char const *argv[])
{
	init();
	int num, n, m;
	scanf("%d%d%d", &num, &m, &n);
	int s = 0, t = n * m * 2 + 1;
	cnt = n * m;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			scanf("%d", &mp[i][j]);
			if(mp[i][j] != 1){
				add((i - 1) * m + j, (i - 1) * m + j + cnt, INF, 0);
			}
			if(mp[i][j] == 2){
				add((i - 1) * m + j, (i - 1) * m + j + cnt, 1, -1);
			}

			int nx = i + 1;
			if(nx <= n){
				add((i - 1) * m + j + cnt, (nx - 1) * m + j, INF, 0);
			}
			int ny = j + 1;
			if(ny <= m){
				add((i - 1) * m + j + cnt, (i - 1) * m + ny, INF, 0);
			}
		}
	}

	add(s, 1, num, 0);
	add(cnt * 2, t, num, 0);
	ek(s, t);
	while(num--){
		id++;
		dfs(0, 0);
	}
    return 0;
}

 

二十、P3355 骑士共存问题

思路:二分图最优匹配,和之前那个最大独立集一个写法,这里权值为1,这里要Dinic当前弧优化,不然会T

#include 
using namespace std;
int INF = 1e9;
const int maxn = 2e6;
struct Edge
{
	int to, next, w;
} edge[maxn];

int k, head[maxn];

void add(int a, int b, int c){
	edge[k].to = b;
	edge[k].w = c;
	edge[k].next = head[a];
	head[a] = k++;

	edge[k].to = a;
	edge[k].w = 0;
	edge[k].next = head[b];
	head[b] = k++;
}
int dis[maxn], cur[maxn];
int bfs(int s, int t){
	queue que;
	for(int i = 0; i <= t; i++){
		dis[i] = 0;
	}
	dis[s] = 1;
	que.push(s);

	while(que.size()){
		int u = que.front();
		que.pop();
		if(u == t){
			return 1;
		}
		for(int i = head[u]; i != -1; i = edge[i].next){
			int to = edge[i].to;
			int w = edge[i].w;
			if(w && !dis[to]){
				dis[to] = dis[u] + 1;
				if(to == t){
					return 1;
				}
				que.push(to);
			}
		}
	}
	return 0;
}

int dfs(int u, int maxf, int t){
	if(u == t){
		return maxf;
	}
	int ret = 0;
	for(int i = cur[u]; i != -1 ; i = edge[i].next){
		cur[u] = i;
		int to = edge[i].to;
		int w = edge[i].w;
		if(dis[to] == dis[u] + 1 && w){
			int mi = min(w, maxf - ret);
			w = dfs(to, mi, t);
			edge[i].w -= w;
			edge[i ^ 1].w += w;
			ret += w;
			if(ret == maxf){
				return ret;
			}
		}
	}
	return ret;
}

int Dinic(int s, int t){
	int ans = 0;
	while(bfs(s, t)){
		for(int i = 0; i <= t; i++){
			cur[i] = head[i];
		}
		ans += dfs(s, INF, t);
	}
	return ans;
}

int mp[300][300];
int tx[] = {-2, -2, -1, 1, 2, 2, 1, -1};
int ty[] = {-1, 1, 2, 2, 1, -1, -2, -2};

void init(){
	k = 0;
	memset(head, -1, sizeof(head));
}
int main(int argc, char const *argv[])
{
	init();
	int n, m;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++){
		int x, y;
		scanf("%d%d", &x, &y);
		mp[x][y] = 1;
	}
	int s = 0, t = n * n + 1;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if((i + j) % 2 == 0 && mp[i][j] == 0){
				add(s, (i - 1) * n + j, 1);
				for(int k = 0; k < 8; k++){
					int nx = i + tx[k];
					int ny = j + ty[k];
					if(nx >= 1 && nx <= n && ny >= 1 && ny <= n && mp[nx][ny] == 0){
						add((i - 1) * n + j, (nx - 1) * n + ny, 1);
					}
				}
			} else if(mp[i][j] == 0){
				add((i - 1) * n + j, t, 1);
			}
		}
	}

	int res = Dinic(s, t);
	printf("%d\n", n * n - res - m);
	return 0;
}

 

你可能感兴趣的:(图论)