一种最小割建模方法

网络流题目中,如果一种物品有两种状态(选或不选),告诉你每一种状态产生的收益/代价,我们就可以通过使用最小割模型来解决这个问题。但是如果物品的状态扩展到了 种,我们就需要用一种新的建图方法。
我们拿两道题目举例:

RatingProgressAward

*TCO2017 Semifinal
题目链接
如果对于积分做了前缀和之后我们发现题目就是要求在满足限制的条件下最大化区间和。
对于一个点 如果我们知道了答案区间在哪 我们可以有三种状态:在答案区间左边,在大安区间内,在答案区间右边。这就是三种状态了。
我们发现在同一区间内 他们的顺序我们是不需要考虑的(内部顺序可以自己排),我们只需要考虑区间与区间的点是否满足拓扑图的要求。
首先对于每个点的三种状态,设积分为 ,我们设计出这样的网络:

如果割掉左边的边就会选择状态 1,后面同理。我们发现这个网络流就十分符合我们题目的要求。
然后我们考虑如何对点和点之间的限制。如果有一对限制 表示 要在前面,我们发现我们其实是不希望在第 行上的割边在 后面。所以我们只需要通过连容量为 的边来强制不能出现不符题意的情况即可。
建图大概如下:

于是这个题目就可以愉快的写出代码了:

/*
 * Author: RainAir
 * Time: 2019-08-01 20:02:30
 */
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 200+5;
const int MAXM = MAXN*MAXN+2;

struct Edge{
    int to,nxt,cap,flow;
}e[MAXM];
int head[MAXN],cur[MAXN],level[MAXN],cnt = 1;
int S,T,N;

inline void add(int u,int v,int w){
    if(!w) return;
    w = std::abs(w);//printf("%d %d %d\n",u,v,w);
    e[++cnt] = (Edge){v,head[u],w,0};head[u] = cnt;
    e[++cnt] = (Edge){u,head[v],0,0};head[v] = cnt;
}

inline bool bfs(){
    std::queue<int> q;
    FOR(i,0,N) level[i] = 0,cur[i] = head[i];
    q.push(S);level[S] = 1;
    while(!q.empty()){
        int v = q.front();q.pop();
        for(int i = head[v];i;i = e[i].nxt){
            if(!level[e[i].to] && e[i].cap > e[i].flow){
                level[e[i].to] = level[v] + 1;
              //  DEBUG(e[i].to);
                if(e[i].to == T) return true;
                q.push(e[i].to);
            }
        }
    }
    return false;
}

inline int dfs(int v,int lim = INT_MAX){
    if(!lim) return 0;
    if(v == T) return lim;int flow;
    for(int &i = cur[v];i;i = e[i].nxt){
        if(e[i].cap > e[i].flow && level[e[i].to] == level[v] + 1){
            if((flow = dfs(e[i].to,std::min(lim,e[i].cap-e[i].flow)))){
                e[i].flow += flow;
                e[i^1].flow -= flow;
                return flow;
            }
        }
    }
    return 0;
}

inline int Dinic(){
    int ans = 0,flow;
    while(bfs()){
        while((flow = dfs(S))) ans += flow;
    }
    return ans;
}

class RatingProgressAward{
public:
    int n,m;
    int maximalProgress(std::vector<int> change,std::vector<int> a,std::vector<int> b){
        n = change.size();m = a.size();
        S = 0,T = 2*n+1;N = T;int sm = 0;
        FOR(i,1,n){
            if(change[i-1] > 0) sm += change[i-1];
            add(S,i,std::max(0,change[i-1]));
            add(i,i+n,std::min(0,change[i-1]));
            add(i+n,i,INT_MAX);
            add(i+n,T,std::max(0,change[i-1]));
        }
        FOR(i,1,m){
            add(b[i-1]+1,a[i-1]+1,INT_MAX);
            add(b[i-1]+1+n,a[i-1]+1+n,INT_MAX);
        }
        return sm-Dinic();
    }
};

FoxAndCity

*SRM590
题目链接
我们观察加入边之后的 数组,发现它需要满足以下条件:

  1. 对于原图中的边 ,满足
  2. 设最长路径为 ,那么 遍取。

我们发现从 可以推出 ,所以我们只需要考虑满足 限制。
我们首先发现一个点的 只有可能是 ,所以相当于是 个不同的状态,并且代价也很好计算出来。
所以这个题的图就很好建出来了。。。沿用上一题的做法,边容量为这个点 为这个状态的时候对答案的贡献。
现在还有一个小问题:处理限制
我们发现只需要让第一行强制选边 (边权为 的状态容量为 ,其余容量为 ),其余的点不允许选择边权为 的状态(容量为 ),然后直接跑网络流就可以了。

/*
 * Author: RainAir
 * Time: 2019-08-01 21:54:11
 */
#include <algorithm>
#include <iostream>
#include <cstring>
#include <climits>
#include <cstdlib>
#include <cstdio>
#include <bitset>
#include <vector>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <map>
#include <set>

#define fi first
#define se second
#define U unsigned
#define P std::pair
#define Re register
#define LL long long
#define pb push_back
#define pw2(x) ((x)*(x))
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(Re int i = a;i <= b;++i)
#define ROF(i,a,b) for(Re int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 500+5;
const int MAXM = 40000 + 5;
int mp[MAXN][MAXN];

struct Edge{
    int to,nxt,cap,flow;
}e[MAXM<<2];
int cnt = 1,head[MAXM],level[MAXM],cur[MAXM];

inline void add(int u,int v,int w){
    //if(v == 122) printf("%d %d %d\n",u,v,w);
    e[++cnt] = (Edge){v,head[u],w,0};head[u] = cnt;
    e[++cnt] = (Edge){u,head[v],0,0};head[v] = cnt;
}

int S,T,N;

inline bool bfs(){
    FOR(i,0,N) cur[i] = head[i];std::fill(level,level+N+1,0);
    std::queue<int> q;q.push(S);level[S] = 1;
    while(!q.empty()){
        int v = q.front();q.pop();
       // if(v >= 111) DEBUG(v);
        for(int i = head[v];i;i = e[i].nxt){
           // if(e[i].to == 122) DEBUG(level[e[i].to]);
           // DEBUG(T);
            if(!level[e[i].to] && e[i].cap > e[i].flow){
                level[e[i].to] = level[v] + 1;
                if(e[i].to == T) return true;
                q.push(e[i].to);
            }
        }
    }
    return false;
}

inline int dfs(int v,int lim=INT_MAX){
    if(v == T) return lim;
    if(!lim) return 0;int flow;
    for(int &i = cur[v];i;i = e[i].nxt){
        if(e[i].cap > e[i].flow && level[e[i].to] == level[v]+1){
            if((flow = dfs(e[i].to,std::min(lim,e[i].cap-e[i].flow)))){
                e[i].flow += flow;
                e[i^1].flow -= flow;
                return flow;
            }
        }
    }
    return 0;
}

inline int Dinic(){
    int ans = 0,flow;
    while(bfs()){
       // DEBUG(flow);
        while((flow = dfs(S))) ans += flow;
    }
    return ans;
}

int n,m;
inline int calc(int x,int y){
    return x+n*(y-1);
}

class FoxAndCity{
public:
    int minimalCost(std::vector<std::string> linked, std::vector<int> want){  
        n = linked.size();m = want.size();
        FOR(i,0,n-1){
            FOR(j,0,n-1){
                mp[i+1][j+1] = linked[i][j] == 'Y';
            }
        }
        S = 0,T = n*n+1,N = T;//DEBUG(N);
  //      DEBUG(T);
        FOR(i,1,n){
            FOR(j,1,n){
                if(j == 1) add(S,calc(i,j),i == 1 ? 0 : INT_MAX);
                else add(calc(i,j-1),calc(i,j),i == 1 ? INT_MAX : pw2(want[i-1]-(j-1))),
                    add(calc(i,j),calc(i,j-1),INT_MAX);
            }
            add(calc(i,n),T,i == 1 ? INT_MAX : pw2(want[i-1]-n));
        }
        FOR(i,1,n){
            FOR(j,1,i-1){
                if(mp[i][j] == 1){
                    FOR(k,2,n){
                        add(calc(i,k),calc(j,k-1),INT_MAX);
                        add(calc(j,k),calc(i,k-1),INT_MAX);
                    }
                }
            }
        }
        return Dinic();
    }
};
全部评论

相关推荐

bg双非本科,方向是嵌入式。这次秋招一共拿到了&nbsp;8&nbsp;个&nbsp;offer,最高年包&nbsp;40w,中间也有一段在海康的实习经历,还有几次国家级竞赛。写这篇不是想证明什么,只是想把自己走过的这条路,尽量讲清楚一点,给同样背景的人一个参考。一、我一开始也很迷茫刚决定走嵌入式的时候,其实并没有一个特别清晰的规划。网上的信息很零散,有人说一定要懂底层,有人说项目更重要,也有人建议直接转方向。很多时候都是在怀疑:1.自己这种背景到底有没有机会2.现在学的东西到底有没有用3.是不是已经开始晚了这些问题,我当时一个都没答案。二、现在回头看,我主要做对了这几件事第一,方向尽早确定,但不把自己锁死。我比较早就确定了嵌入式这个大方向,但具体做哪一块,是在项目、竞赛和实习中慢慢调整的,而不是一开始就给自己下结论。第二,用项目和竞赛去“证明能力”,而不是堆技术名词。我不会刻意追求学得多全面,而是确保自己参与的每个项目,都能讲清楚:我负责了什么、遇到了什么问题、最后是怎么解决的。第三,尽早接触真实的工程环境。在海康实习的那段时间,对我触动挺大的。我开始意识到,企业更看重的是代码结构、逻辑清晰度,以及你能不能把事情说清楚,而不只是会不会某个知识点。第四,把秋招当成一个需要长期迭代的过程。简历不是一次写完的,面试表现也不是一次就到位的。我会在每次面试后复盘哪些问题没答好,再针对性补。三、我踩过的一些坑现在看也挺典型的:1.一开始在底层细节上纠结太久,投入产出比不高2.做过项目,但前期不会总结,导致面试表达吃亏3.早期有点害怕面试,准备不充分就去投这些弯路走过之后,才慢慢找到节奏。四、给和我背景相似的人一点建议如果你也是双非,准备走嵌入式,我觉得有几件事挺重要的:1.不用等“准备得差不多了”再投2.项目一定要能讲清楚,而不是做完就算3.不要只盯着技术,多关注表达和逻辑很多时候,差的不是能力,而是呈现方式。五、写在最后这篇总结不是标准答案,只是我个人的一次复盘。后面我会陆续把自己在嵌入式学习、竞赛、实习和秋招中的一些真实经验拆开来讲,希望能对后来的人有点帮助。如果你正好也在这条路上,希望你能少走一点弯路。
x_y_z1:蹲个后续
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务