【学术篇】网络流24题——方格取数加强版
将近一年没写(别问我为啥)网络流都忘光啦OvO现在决定来重拾一下...
所以从哪道题开始哩?
"等等, 这不是个dp大水题么?"
不好意思 放错链接了 应该是这个
Emmmm很明显唯一的区别就是把2改为了\(k\)... 可我们总不能跑一个\(2k\)维dp吧? 而且还不知道\(k\)是多少...
那咋整呢...卖什么关子啊←_←标题都说是网络流了还用想别的?
我们就考虑网络流吧... 网络流主要的问题就是建图啦~
建图的时候就是考虑题目中的各个条件怎么转化为边的限制.
可以把每个点拆成两个然后建边.
- 在拆开的两个点之间建一条费用为\(a[i][j]\), 流量为1的边, 表示这个权值可以且仅可以取一次.
- 再在拆开的两个点之间建一条费用为0, 流量为\(\infty\)的边, 表示这个点可以走很多次, 但是走的后几次是取不到权值的.
- 每个点的右边的点向右边或下边相邻的点的左边连一条费用为0, 流量为\(\infty\)的边, 就是表示从这个点往下/往右走了啊
- 从源点\(S\)向左上角点的左边连一条费用为0, 流量为\(k\)的边, 表示一共有\(k\)次要走.
- 从汇点\(T\)向右下角点的右边连一条费用为0, 流量为\(k\)的边, 表示走完.
- (不过好像源点和汇点连的边只要有一个流量是\(k\)起到限制作用, 另一个写\(\infty\)或别的什么比\(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..
这个板子稍微改改可以水掉的题目(换句话说数据比较适合爆炸的程序对拍的:
解释一下代码里面出现的过多的::是用来触发clang-complete的自动补全的...
因为可以同时补全参数类型.. 超级好用的一个功能, 那么为什么不用呢XD
所以就多出来很多::又懒得删了 反正也不会CE, 就这样呗OvO