【学术篇】网络流经典题--方格取数加强版

将近一年没写(别问我为啥)网络流都忘光啦OvO现在决定来重拾一下…
所以从哪道题开始哩?

就决定是你了!

“等等, 这不是个dp大水题么?”
不好意思 放错链接了 应该是这个

就决定是你了!

Emmmm很明显唯一的区别就是把2改为了 k … 可我们总不能跑一个2k维dp吧? 而且还不知道 k 是多少…
那咋整呢…卖什么关子啊←_←标题都说是网络流了还用想别的?

我们就考虑网络流吧… 网络流主要的问题就是建图啦~
建图的时候就是考虑题目中的各个条件怎么转化为边的限制.
可以把每个点拆成两个然后建边.
- 在拆开的两个点之间建一条费用为a[i][j], 流量为1的边, 表示这个权值可以且仅可以取一次.
- 再在拆开的两个点之间建一条费用为0, 流量为 的边, 表示这个点可以走很多次, 但是走的后几次是取不到权值的.
- 每个点的右边的点向右边或下边相邻的点的左边连一条费用为0, 流量为 的边, 就是表示从这个点往下/往右走了啊
- 从源点 S 向左上角点的左边连一条费用为0, 流量为k的边, 表示一共有 k 次要走.
- 从汇点T向右下角点的右边连一条费用为0, 流量为 k 的边, 表示走完.
- (不过好像源点和汇点连的边只要有一个流量是k起到限制作用, 另一个写 或别的什么比 k 大的数也可以?

以样例为例我们建出来的图应该长这样(由于从S出发只有一条边我就横过来了~~
建图示例

这样图就建好了, 然后我们跑一遍最大费用最大流就好了… 但是这玩意怎么跑呢?
把每条边的费用设置成相反数然后跑最小费用最大流不就完了么←_← 最后结果是个负的再倒过来不就好了…
(所以这种方法不能用来处理负权… 所以题目给的都是正整数…

之后贴板子就好了… 不过再扯点别的…
比如我忘记费用流怎么写了于是决定直接去学zkw费用流..
之前也看过zkw(orz)的blog但是个人来讲并不是很喜欢里面的风格OvO
然后baidu一下”zkw费用流”发现第一条是一位dalao改的zkw费用流,(讲的挺好的大家都去学就好了, 我就不发表自己的拙见了)我就直接按自己的码风改(chao)了一遍, 去刷了一下模板题… 本来昨天晚上想发出来的结果光顾着颓废了就耽搁了..
那就不用发板子了直接跟着题贴出来就好咯…
不过写起来和dinic是真的像..(正好复习了一下同样忘光的dinic..

代码:

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=5050;
const int M=30050;
const int INF=0x7f7f7f7f;
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar())a=a*10+c-48;return a;
}
struct edge{
    int to,next,cost,flow;
}e[M]; int v[N],tot=1;
void buildedge(int x,int y,int cost,int flow){
    e[++tot].to=y; e[tot].next=v[x]; v[x]=tot; e[tot].cost=cost; e[tot].flow=flow;
    e[++tot].to=x; e[tot].next=v[y]; v[y]=tot; e[tot].cost=-cost; e[tot].flow=0;
}
int a[52][52],d[N],s,t,n,k,cost;
bool vis[N];
queue<int> q;
inline bool spfa(const int& s,const int& t){
    memset(vis,0,sizeof(vis));
    memset(d,0x7f,sizeof(d));
    vis[t]=1; d[t]=1; q.push(t);
    while(!q.empty()){
        int x=q.front(); q.pop(); vis[x]=0;
        for(int i=v[x];i;i=e[i].next)
            if(e[i^1].flow&&d[e[i].to]>d[x]-e[i].cost){
                d[e[i].to]=d[x]-e[i].cost;
                if(!vis[e[i].to]) 
                    q.push(e[i].to),vis[e[i].to]=1;
            }
    }
    return d[s]<INF;
}
int dfs(int x,int mx,int s=0){ vis[x]=1;
    if(x==t) return mx; int k;
    for(int i=v[x];i;i=e[i].next)
        if(!vis[e[i].to]&&e[i].flow&&d[e[i].to]==d[x]-e[i].cost){
            k=::dfs(e[i].to, ::min(e[i].flow, mx-s));
            if(k) cost+=k*e[i].cost,e[i].flow-=k,e[i^1].flow+=k,s+=k;
            if(s==mx) break;
        }
    return s;
}
int mcmf(int flow=0){
    while(::spfa(s, t)){ vis[t]=1;
        while(vis[t]){
            memset(vis,0,sizeof(vis));
            flow+=dfs(s,INF);
        }
    } return flow;
}
int main(){ n=gn(),k=gn(); 
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            a[i][j]=gn();
    int nn=n*n; s=0; t=nn<<1|1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j){
            int x=(i-1)*n+j;
            ::buildedge(x, x+nn, -a[i][j], 1);
            ::buildedge(x, x+nn, 0, INF);
            if(i<n) ::buildedge(x+nn, x+n, 0, INF);
            if(j<n) ::buildedge(x+nn, x+1, 0, INF);
        }
    ::buildedge(s, 1, 0, k); ::buildedge(t-1, t, 0, k);
    mcmf(); printf("%d",-cost);
}

结果就这么个昨天刚学的板子今天再写就写疵了.. mdzz..

这个板子稍微改改可以水掉的题目(换句话说数据比较适合爆炸的程序对拍的:
- luogu2405 (这个不用改2333
- luogu1004 (把k=gn()改成k=2, 然后把读入一改就好
- luogu1006 (还是k=2, 改改读入就行 这俩好像本来就是一个题吧?

解释一下代码里面出现的过多的::是用来触发clang-complete的自动补全的…
因为可以同时补全参数类型.. 超级好用的一个功能, 那么为什么不用呢XD
所以就多出来很多::又懒得删了 反正也不会CE, 就这样呗OvO

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值