网络流24题(持续更新)

网络流——难在建图

经典的最小割模型——二分图最小点权覆盖

骑士共存问题

题目描述

在一个 n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘上某些方格设置了障碍,骑士不得进入

对于给定的 n*n 个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击

输入输出格式

输入格式:

第一行有 2 个正整数n 和 m (1<=n<=200, 0<=m<n2),分别表示棋盘的大小和障碍数。接下来的 m 行给出障碍的位置。每行 2 个正整数,表示障碍的方格坐标。

输出格式:

将计算出的共存骑士数输出

输入输出样例

输入样例#1: 
3 2
1 1
3 3
输出样例#1:
5

题解:此题就是求互相攻击的格子之间的最大匹配;
统计可放格子的个数,在将可互相攻击的格子建边,用格子个数-最小割即可,
#include <iostream>
#include<cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int N, M, tot, S, T;
const int maxn = 4e4+10;
const int inf = 1000000008;
int f[205][205],mp[205][205];
int zl[9][2] = {{-2,1},{-1,2},{-1,-2},{-2,-1},{1,-2},{1,2},{2,-1},{2,1}};
struct Netflow{
    int dis[maxn],h[maxn],hh[maxn];

    struct edge{int v,nxt,f;}G[800005];
    void add(int u, int v, int f){
        G[++tot].v = v;
        G[tot].nxt = h[u];
        h[u] = tot;
        G[tot].f = f;
        G[++tot].v = u;
        G[tot].nxt = h[v];
        h[v] = tot;
        G[tot].f = 0;
    }

    bool bfs(){
        memset(dis, 0, sizeof(dis));
        queue <int> Q;
        dis[S] = 1;
        Q.push(S);
        while(!Q.empty()){
            int u = Q.front();Q.pop();
            for(int i = h[u]; i; i = G[i].nxt){
                int v = G[i].v;
                if(!dis[v] && G[i].f)dis[v] = dis[u] + 1, Q.push(v);

            }
        }
        return dis[T] != 0;

    }
    int dfs(int u, int q){
        if(u == T || !q)return q;
        int ret = 0;
        for(int i = hh[u]; i; i = G[i].nxt){
            int v = G[i].v;
            if(dis[v] == dis[u]+1 && G[i].f){
                int dd = dfs(v, min(q, G[i].f));
                ret += dd;
                q -= dd;
                G[i].f -= dd;
                G[i^1].f += dd;
                hh[u] = i;
            }
        }
        if(!ret)dis[u] = -1;
        return ret;
    }
    int dinic(){
        int ans = 0;
        while(bfs()){
            for(int i = 0; i <= T; i++)hh[i] = h[i];
            ans += dfs(S, inf);
        }
        return ans;
    }

}Tr;
int main()
{

    scanf("%d%d",&N,&M);
    S = 0, T = N*N + 1, tot = 1;
    int q = 0, idx = 0;
    for(int i =  1; i <= N; i++)
        for(int j = 1; j <= N; j++)
            f[i][j] = ++idx;
    for(int i = 1; i <= M; i++){
        int x, y;
        scanf("%d%d",&x,&y);
        mp[x][y] = 1;
    }
    for(int i = 1; i <= N; i++)
    for(int j = 1; j <= N; j++)
    if(mp[i][j] == 1)q++;
    else if((i+j)%2){
        Tr.add(S, f[i][j], 1);
        for(int k = 0; k < 9; k++){
            int x = i + zl[k][0], y = j + zl[k][1];
            if(x>=1&&x<=N&&y>=1&&y<=N&& !mp[x][y])
                Tr.add(f[i][j], f[x][y], inf);
        }
    }else Tr.add(f[i][j], T, 1);
    printf("%d\n",N*N-q-Tr.dinic());



}
View Code

 

 方格取数问题

题目描述

在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。

输入输出格式

输入格式:

第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。

输出格式:

程序运行结束时,将取数的最大总和输出

输入输出样例

输入样例#1: 
3 3
1 2 3
3 2 3
2 3 1 
输出样例#1:
11

说明

m,n<=100

题解:

将棋盘黑白染色形成一个二分图
就成了一个经典的模型了
直接最小割解决就可以了
 
#include<bits/stdc++.h>
using namespace std;
#define inf 1000000008
const int maxn = 10005;
int a[4][2] = {{0,1},{1,0},{-1,0},{0,-1}};
int tot = 1,n,m,S,T, dis[maxn], head[maxn], hh[maxn];
bool vis[maxn];
struct Point{
    int id, key;
}mp[maxn/100][maxn/100];
struct edge{
    int to, f, nxt;
}G[maxn * 8];
void add(int u, int v, int f){    
    G[++tot].nxt = head[u];
    G[tot].to = v;
    G[tot].f = f;
    head[u] = tot;
    
    G[++tot].nxt = head[v];
    G[tot].to = u;
    G[tot].f = 0;
    head[v] = tot;
}
bool bfs(){
    memset(vis, 0, sizeof(vis));
    for(int i = 0; i <= T; i++)dis[i] = inf;
    queue <int> Q;
    Q.push(S);
    dis[S] = 0;
    vis[S] = 1;
    while(!Q.empty()){
        int u = Q.front();
        Q.pop();
        
        for(int i = head[u]; i; i = G[i].nxt){
            int v = G[i].to;
            if(G[i].f && !vis[v]){
                vis[v] = 1;
                dis[v] = dis[u] + 1;
                Q.push(v);
            }
        }
    }
    return vis[T];
}
int dfs(int s, int q){
    if(s == T || !q)return q;
    int ret = 0;
    for(int i = hh[s]; i; i = G[i].nxt){
        int v = G[i].to;
        if(G[i].f && dis[v] == dis[s] + 1){
            int dd = dfs(v, min(q, G[i].f));
            //printf("%d %d %d %d\n",s, v, G[i].f, dd);
            q -= dd;
            G[i].f -= dd;
            G[i^1].f += dd;
            ret += dd;
            hh[s] = i;
        }
    }
    if(!ret) dis[s] = -1;
    //printf("%d     hahahahaha\n",ret);
    return ret;
}
int  dinic(){
    int ans = 0;
    while(bfs()){
        for(int i = 0; i <= T; i++)hh[i] = head[i];
        ans += dfs(S, inf);
    }
    return ans;
}
int main(){
    int sum = 0, cnt = 0;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++)
        if(i%2) for(int j = 1; j <= m; j++)mp[i][j].id = ++cnt;
        else for(int j = m; j >= 1; j--)mp[i][j].id = ++cnt;
    S = 0, T = cnt + 1;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            scanf("%d",&mp[i][j].key), sum += mp[i][j].key;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++){
            int id1 = mp[i][j].id, val1 = mp[i][j].key;
            if(id1 % 2){
                add(S, id1, val1);
                for(int k = 0; k < 4; k++){
                    int x = i + a[k][0], y = j + a[k][1];
                    int id2 = mp[x][y].id;
                    if(x && y && x <= n && y <= m && !(id2 % 2))
                        add(id1, id2, inf);
                
                }
            }
            else add(id1, T, val1);                                                                            
        }
    
    int ans = dinic();
    printf("%d\n",sum - ans);
}
View Code

方格取数加强版 

题目描述

给出一个n*n的矩阵,每一格有一个非负整数Aij,(Aij <= 1000)现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0,这样一共走K次,现在要求K次所达到的方格的数的和最大

输入输出格式

输入格式:

第一行两个数n,k(1<=n<=50, 0<=k<=10)

接下来n行,每行n个数,分别表示矩阵的每个格子的数

输出格式:

一个数,为最大和

输入输出样例

输入样例#1: 
3 1
1 2 3
0 2 1
1 4 2
输出样例#1:
11

说明

每个格子中的数不超过1000

题解:对于多种选择拆点,利用网络流的性质

•把每个格子拆点,连一条费用为权值,流量为1的边,代表取走
•再连一条费用为0,流量为inf的边,代表路过
•从左上到右下按顺序建出一个费用为0,流量为inf的网格图
•超级源点向左上角的格子连一条流量为k的边
•跑最大费用最大流
#include <iostream>
#include<cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int N, M, tot, S, T;
const int maxn = 5e3+10, maxm = 1e5;
const int inf = 1000000008;
struct Netflow{
    int dis[maxn],h[maxn],hh[maxn],inq[maxn],prep[maxn],pree[maxm];

    struct edge{int v,nxt,f,w;}G[maxm];
    void add(int u, int v, int f, int w){
        G[++tot].v = v;
        G[tot].nxt = h[u];
        h[u] = tot;
        G[tot].f = f;
        G[tot].w = -w;

        G[++tot].v = u;
        G[tot].nxt = h[v];
        h[v] = tot;
        G[tot].f = 0;
        G[tot].w = w;
    }

    bool spfa(){
        memset(dis, 127, sizeof(dis));
        memset(inq, 0, sizeof(inq));
        queue <int> Q;
        dis[S] = 0;
        inq[S] = 1;
        Q.push(S);
        while(!Q.empty()){
            int u = Q.front();
            Q.pop();
            inq[u] = 0;
            for(int i = h[u]; i; i = G[i].nxt){
                int v = G[i].v;
                if(dis[v] > dis[u] + G[i].w && G[i].f){
                    prep[v] = u;
                    pree[v] = i;
                    dis[v] = dis[u] + G[i].w;
                    if(!inq[v]){
                        Q.push(v);
                        inq[v] = 1;
                    }
                }
            }
        }
        return dis[T];

    }

    int hunger(){
        int mincost = 0, k = 0;
        while(spfa() && k < M){
            k++;
            int delta = inf, u = T;
            while(u != S){
                delta = min(delta, G[pree[u]].f);
                u = prep[u];

            }
            u = T;
            while(u != S){
                G[pree[u]].f -= delta;
                G[pree[u]^1].f += delta;
                u = prep[u];
            }
           mincost += delta * dis[T];
        }
        return mincost;
    }

}Tr;
int main()
{

    scanf("%d%d",&N,&M);
    S = 0, T = 2*N*N + 1, tot = 1;
    int idx = 0;
    for(int i =  1; i <= N; i++)
        for(int j = 1; j <= N; j++){
            int w;
            scanf("%d",&w);
            ++idx;
            Tr.add(idx, idx+N*N, 1, w);
            Tr.add(idx, idx+N*N, inf, 0);
            if(j!=N)Tr.add(idx+N*N, idx+1, inf, 0);
            if(i!=N)Tr.add(idx+N*N, idx+N, inf, 0);
        }
    Tr.add(S, 1, inf, 0);
    Tr.add(2*N*N, T, inf, 0);
    printf("%d\n",-Tr.hunger());



}
View Code
 

题目描述 

太空飞行计划问题

W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的集合I={I1,I2,…In}。实验Ej需要用到的仪器是I的子集RjÍI。配置仪器Ik的费用为ck美元。实验Ej的赞助商已同意为该实验结果支付pj美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。

对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。

输入输出格式

输入格式:

第1行有2 个正整数m和n。m是实验数,n是仪器数。接下来的m 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的n个数是配置每个仪器的费用。

输出格式:

第1 行是实验编号;第2行是仪器编号;最后一行是净收益。

输入输出样例

输入样例#1: 
2 3
10 1 2
25 2 3
5 6 7
输出样例#1: 
1 2
1 2 3
17
题解:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int inf = 1000000008;
int n,m,flag,S,T;
int read()
{
    char c;int r=0;
    while (c<'0' || c>'9') c=getchar();
    while (c>='0' && c<='9')
    {
        r=r*10+c-'0';
        c=getchar();
    }
    if (c=='\n') flag=1;
    return r;
}
vector <int> tools[maxn];
struct Netflow{
    int dis[maxn], tot, head[maxn], vis[maxn], hh[maxn], no[maxn], tool[maxn];
    void init(){
        tot = 1;
    }
    struct edge{
        int v,f,nxt;
    }G[2 * (50 * 50 + 10000)];
    void aad(int u, int v, int f){
        G[++tot].v = v;
        G[tot].nxt = head[u];
        G[tot].f = f;
        head[u] = tot;

        G[++tot].v = u;
        G[tot].nxt = head[v];
        G[tot].f = 0;
        head[v] = tot;
    }
    bool bfs(){
        memset(dis, 0, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        queue <int> Q;
        Q.push(S);
        vis[S] = 1;
        while(!Q.empty()){
            int u = Q.front();Q.pop();
            for(int i = head[u]; i; i = G[i].nxt){
                int v = G[i].v;
                if(G[i].f && !vis[v]){
                    Q.push(v);
                    dis[v] = dis[u] + 1;
                    vis[v] = 1;
                }

            }
        }
        return dis[T];

    }
    int dfs(int u, int q){
        if(u == T || !q)return q;
        int ret = 0;
        for(int i = hh[u]; i; i = G[i].nxt){
            int v = G[i].v;
            if(G[i].f && dis[v] == dis[u] + 1){
                int dd = dfs(v, min(q, G[i].f));
                q -= dd;
                ret += dd;
                G[i].f -= dd;
                G[i^1].f += dd;
                hh[u] = i;//?
            }
        }
        if(!ret)dis[u] = -1;
        return ret;
    }
    int dinic(){
        int ans = 0;
        while(bfs()){
            for(int i = 0; i <= T; i++)hh[i] = head[i];
            ans += dfs(S, inf);
        }
        return ans;

    }
    void print(){
        for(int i = 1; i <= n; i++)
            if(!dis[i])no[i] = 1;
        for(int i = 1; i <= n; i++){
            if(!no[i]){
                printf("%d ",i);
                for(int j = 0; j < tools[i].size(); j++)
                    tool[tools[i][j]] = 1;
            }
        }
        cout<<endl;
        for(int i = 1; i <= m; i++)
            if(tool[i])printf("%d ",i);
        cout<<endl;
    }
}Tr;

int main(){
    scanf("%d%d",&n,&m);
    S = 0, T = n+m+1;
    int q = 0;
    Tr.init();
    for(int i = 1; i <= n; i++){
        flag = 0;
        int w;
        scanf("%d",&w);
        q += w;
        while(!flag){
            int x = read();
            tools[i].push_back(x);
            Tr.aad(i, n+x, inf);
        }
        Tr.aad(S, i, w);
    }
    for(int i = 1; i <= m; i++){
        int w;scanf("%d",&w);
        Tr.aad(i+n, T, w);
    }
    q -= Tr.dinic();
    Tr.print();
    printf("%d\n",q);



}
View Code

试题库问题

题目描述

«问题描述:

假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m 道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算法。

«编程任务:

对于给定的组卷要求,计算满足要求的组卷方案。

输入输出格式

输入格式:

第1行有2个正整数k和n (2 <=k<= 20, k<=n<= 1000)

k 表示题库中试题类型总数,n 表示题库中试题总数。第2 行有k 个正整数,第i 个正整数表示要选出的类型i的题数。这k个数相加就是要选出的总题数m。接下来的n行给出了题库中每个试题的类型信息。每行的第1 个正整数p表明该题可以属于p类,接着的p个数是该题所属的类型号。

输出格式:

第i 行输出 “i:”后接类型i的题号。如果有多个满足要求的方案,只要输出1个方案。如果问题无解,则输出“No Solution!”。

输入输出样例

输入样例#1: 
3 15
3 3 4
2 1 2
1 3
1 3
1 3
1 3
3 1 2 3
2 2 3
2 1 3
1 2
1 2
2 1 2
2 1 3
2 1 2
1 1
3 1 2 3
输出样例#1: 
1: 1 6 8
2: 7 9 10
3: 2 3 4 5

题解:将源点向每一道题建一条流量为1的边,代表1个题只配一个种类;再将每道题和他可以选的种类连一条流量为1
的边,每个种类向汇点连一条流量为需要的题数的边,看最大流跑出来满不满足条件
#include <bits/stdc++.h>

using namespace std;
const int maxn = 1080,maxm = 90000,inf = 100000008;
int S, T, n, k, tot = 1, w, r[maxn];
bool vis[maxn];

struct Netflow{
    struct edge{
        int nxt, to, f;
    }G[maxm];
    int head[maxn],dis[maxn],hh[maxn];
    vector <int> P[maxn];
    void add(int u,int v,int f){
        G[++tot].nxt = head[u];
        G[tot].f = f;
        G[tot].to = v;
        head[u] = tot;

        G[++tot].nxt = head[v];
        G[tot].f = 0;
        G[tot].to = u;
        head[v] = tot ;
    }
    bool bfs(){
        memset(vis, 0, sizeof(vis));
        memset(dis, 0, sizeof(dis));
        vis[S] = 1;
        dis[S] = 0;
        queue <int> Q;
        Q.push(S);

        while(!Q.empty()){
            int u = Q.front();
            Q.pop();

            for(int i = head[u]; i; i = G[i].nxt){
                int v = G[i].to;
                if(!vis[v] && G[i].f){
                    Q.push(v);
                    vis[v] = 1;
                    dis[v] = dis[u] + 1;
                }
            }
        }
        return vis[T];
    }
    int dfs(int u,int q){
        if(u == T || !q)return q;
        int ret = 0;
        for(int i = hh[u]; i; i = G[i].nxt){
            int v = G[i].to;
            if(G[i].f && dis[v] == dis[u] + 1){
                int dd = dfs(v, min(G[i].f, q));
                G[i].f -= dd;
                G[i^1].f += dd;
                ret += dd;
                q -= dd;
                hh[u] = i;
            }
        }
        if(!ret)dis[u] = -1;
        return ret;
    }
    bool inic(){
        int ans = 0;
        while(bfs()){
            for(int i = 0; i <= n+k+1; i++)hh[i] = head[i];
            ans += dfs(S, inf);
        }
        return ans == w;
    }
    void print(){
        for(int i = 1; i <= n; i++){
            for(int j = head[i]; j; j = G[j].nxt){
                int v = G[j].to;
                if(v != S && !G[j].f)P[v].push_back(i);
            }
        }
        for(int i = 1+n; i <= n+k; i++){
            sort(P[i].begin(), P[i].end());
            printf("%d: ",i-n);
            for(int j = 0; j < P[i].size(); j++)printf("%d ",P[i][j]);
            printf("\n");
        }
    }

};
Netflow Tr;
int main()
{
    scanf("%d%d",&k,&n);
    S = 0, T = n + k + 1;
    for(int i = 1+n; i <= k+n; i++){
        scanf("%d",&r[i]);
        w += r[i];
        Tr.add(i, T, r[i]);
    }
    for(int i = 1; i <= n; i++){
        Tr.add(S, i, 1);
        int p, g;
        scanf("%d",&p);
        while(p--){
            scanf("%d",&g);
            Tr.add(i, g+n, 1);
        }
    }
    if(Tr.inic())Tr.print();
    else printf("No Solution!\n");
    return 0;
}
View Code
 
        

圆桌问题

题目描述

假设有来自m 个不同单位的代表参加一次国际会议。每个单位的代表数分别为ri (i =1,2,……,m)。

会议餐厅共有n 张餐桌,每张餐桌可容纳ci (i =1,2,……,n)个代表就餐。

为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。试设计一个算法,给出满足要求的代表就餐方案。

对于给定的代表数和餐桌数以及餐桌容量,编程计算满足要求的代表就餐方案。

 
        

输入输出格式

输入格式:

第1 行有2 个正整数m 和n,m 表示单位数,n 表示餐桌数,1<=m<=150, 1<=n<=270。

第2 行有m 个正整数,分别表示每个单位的代表数。

第3 行有n 个正整数,分别表示每个餐桌的容量。

输出格式:

如果问题有解,第1 行输出1,否则输出0。接下来的m 行给出每个单位代表的就餐桌号。如果有多个满足要求的方案,只要输出1 个方案。

输入输出样例

输入样例#1: 
4 5
4 5 3 5
3 5 2 6 4
输出样例#1: 
1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5
 
        
#include <bits/stdc++.h>

using namespace std;
const int maxn = 600,maxm = 90000,inf = 100000008;
int S, T, n, m, tot = 1, w;
bool vis[maxn];

struct Netflow{
    struct edge{
        int nxt, to, f;
    }G[maxm];
    int head[maxn],dis[maxn],hh[maxn],P[maxn];
    void add(int u,int v,int f){
        G[++tot].nxt = head[u];
        G[tot].f = f;
        G[tot].to = v;
        head[u] = tot;

        G[++tot].nxt = head[v];
        G[tot].f = 0;
        G[tot].to = u;
        head[v] = tot ;
    }
    bool bfs(){
        memset(vis, 0, sizeof(vis));
        memset(dis, 0, sizeof(dis));
        vis[S] = 1;
        dis[S] = 0;
        queue <int> Q;
        Q.push(S);

        while(!Q.empty()){
            int u = Q.front();
            Q.pop();

            for(int i = head[u]; i; i = G[i].nxt){
                int v = G[i].to;
                if(!vis[v] && G[i].f){
                    Q.push(v);
                    vis[v] = 1;
                    dis[v] = dis[u] + 1;
                }
            }
        }
        return vis[T];
    }
    int dfs(int u,int q){
        if(u == T || !q)return q;
        int ret = 0;
        for(int i = hh[u]; i; i = G[i].nxt){
            int v = G[i].to;
            if(G[i].f && dis[v] == dis[u] + 1){
                int dd = dfs(v, min(G[i].f, q));
                G[i].f -= dd;
                G[i^1].f += dd;
                ret += dd;
                q -= dd;
                hh[u] = i;
            }
        }
        if(!ret)dis[u] = -1;
        return ret;
    }
    bool inic(){
        int ans = 0;
        while(bfs()){
            for(int i = 0; i <= n+m+1; i++)hh[i] = head[i];
            ans += dfs(S, inf);
        }
        return ans == w;
    }
    void print(){
        for(int i = 1; i <= n; i++){
            int k = 0;
            for(int j = head[i]; j; j = G[j].nxt){
                int v = G[j].to;
                if(v != S && !G[j].f)P[++k] = v;
            }
            sort(P+1, P+1+k);
            for(int j = 1; j <= k; j++)printf("%d ",P[j]-n);
            printf("\n");
        }
    }

};
Netflow Tr;
int main()
{
    scanf("%d%d",&n,&m);
    S = 0, T = n + m + 1;
    for(int i = 1; i <= n; i++){
        int r;
        scanf("%d",&r);
        Tr.add(S, i, r);
        w += r;
        for(int j = n+1; j <= n+m; j++)
            Tr.add(i, j, 1);
    }
    for(int i = n+1; i <= n+m; i++){
        int c;
        scanf("%d",&c);
        Tr.add(i, T, c);
    }
    if(Tr.inic()){
        printf("1\n");
        Tr.print();
    }
    else printf("0\n");
    return 0;
}
View Code
 
         
        
 

P1251 餐巾计划问题

题目描述

一个餐厅在相继的 NN 天里,每天需用的餐巾数不尽相同。假设第 ii 天需要 r_iri 块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 pp 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 nn 天( n>mn>m ),其费用为 ss 分( s<fs<f )。

每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。

试设计一个算法为餐厅合理地安排好 NN 天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。

输入输出格式

输入格式:

由标准输入提供输入数据。文件第 1 行有 1 个正整数 NN ,代表要安排餐巾使用计划的天数。

接下来的 NN 行是餐厅在相继的 NN 天里,每天需用的餐巾数。

最后一行包含5个正整数 p,m,f,n,sp,m,f,n,s 。 pp 是每块新餐巾的费用; mm 是快洗部洗一块餐巾需用天数; ff 是快洗部洗一块餐巾需要的费用; nn 是慢洗部洗一块餐巾需用天数; ss 是慢洗部洗一块餐巾需要的费用。

输出格式:

将餐厅在相继的 N 天里使用餐巾的最小总花费输出

输入输出样例

输入样例#1:  复制
3
1 7 5 
11 2 2 3 1
输出样例#1:  复制
134

说明

N<=2000

ri<=10000000

p,f,s<=10000

时限4s

题解:这道题的建图就是一股清流,非常难想,以下摘自洛谷

首先,我们拆点,将一天拆成晚上和早上,每天晚上会受到脏餐巾(来源:当天早上用完的餐巾,在这道题中可理解为从原点获得),每天早上又有干净的餐巾(来源:购买、快洗店、慢洗店)。

1.从原点向每一天晚上连一条流量为当天所用餐巾x,费用为0的边,表示每天晚上从起点获得x条脏餐巾。

2.从每一天早上向汇点连一条流量为当天所用餐巾x,费用为0的边,每天白天,表示向汇点提供x条干净的餐巾,流满时表示第i天的餐巾够用 。

3.从每一天晚上向第二天晚上连一条流量为INF,费用为0的边,表示每天晚上可以将脏餐巾留到第二天晚上(注意不是早上,因为脏餐巾在早上不可以使用)。

4.从每一天晚上向这一天+快洗所用天数t1的那一天早上连一条流量为INF,费用为快洗所用钱数的边,表示每天晚上可以送去快洗部,在地i+t1天早上收到餐巾 。

5.同理,从每一天晚上向这一天+慢洗所用天数t2的那一天早上连一条流量为INF,费用为慢洗所用钱数的边,表示每天晚上可以送去慢洗部,在地i+t2天早上收到餐巾 。

6.从起点向每一天早上连一条流量为INF,费用为购买餐巾所用钱数的边,表示每天早上可以购买餐巾 。 注意,以上6点需要建反向边!3~6点需要做判断(即连向的边必须<=n)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 5000, maxm = 2000005;
const ll inf = 10000000000008LL;
int S, T;
ll need[maxn];
struct Netflow{
    int h[maxn], tot, pre[maxn], pree[maxn], inq[maxn];
    ll dis[maxn];
    struct edge{int v, nxt; ll f, w;}G[maxm];

    void init(){
        tot = 1;
    }
    void add(int u, int v, ll f, ll w){
        G[++tot].v = v; G[tot].f = f; G[tot].w = w;
        G[tot].nxt = h[u]; h[u] = tot;

        G[++tot].v = u; G[tot].f = 0; G[tot].w = -w;
        G[tot].nxt = h[v]; h[v] = tot;
    }

    bool spfa(){
        queue <int> Q;
        memset(dis, 127, sizeof(dis));
        memset(inq, 0, sizeof(inq));
        Q.push(S);
        inq[S] = 1; dis[S] = 0;

        while(!Q.empty()){
            int u = Q.front(); Q.pop(); inq[u] = 0;

            for(int i = h[u]; i; i = G[i].nxt){
                int v = G[i].v;
                if(G[i].f && dis[v] > dis[u] + G[i].w){
                    dis[v] = dis[u] + G[i].w;
                    pre[v] = u; pree[v] = i;
                    if(!inq[v])Q.push(v), inq[v] = 1;
                }
            }
        }

        return dis[T] < inf;
    }

    ll augment(){
        ll ans = 0;
        while(spfa()){
            int u = T; ll delta = inf;
            while(u != S){
                delta = min(delta, G[pree[u]].f);
                u = pre[u];
            }
            u = T;
            while(u != S){
                G[pree[u]].f -= delta;
                G[pree[u]^1].f += delta;
                u = pre[u];
            }
            //printf("%d %d\n",delta, dis[T]);
            ans += delta * dis[T];
        }
        return ans;
    }

}Tr;
int main()
{
    int N; ll p, m, f, n, s;
    scanf("%d", &N);
    for(int i = 1; i <= N; i++)scanf("%lld", &need[i]);
    scanf("%lld%lld%lld%lld%lld", &p, &m, &f, &n, &s);
    Tr.init();// i day i+N night
    S = 0, T = N*2+1;
    for(int i = 1; i < N; i++){
        Tr.add(S, i+N, need[i], 0);
        Tr.add(i+N, i+N+1, inf, 0);
    }
    Tr.add(S, N+N, need[N], 0);
    for(int i = 1; i <= N; i++){
        Tr.add(i, T, need[i], 0);
        Tr.add(S, i, inf, p);
        if(i + m <= N)Tr.add(i+N, i+m, inf, f);
        if(i + n <= N)Tr.add(i+N, i+n, inf, s);
    }
    ll ans = Tr.augment();
    printf("%lld\n", ans);
    return 0;
}
View Code

猜你喜欢

转载自www.cnblogs.com/EdSheeran/p/8718577.html