【学术篇】网络流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..

这个板子稍微改改可以水掉的题目(换句话说数据比较适合爆炸的程序对拍的:

  • luogu2405 (这个不用改2333
  • luogu1004 (把k=gn()改成k=2, 然后把读入一改就好
  • luogu1006 (还是k=2, 改改读入就行 这俩好像本来就是一个题吧?

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

原文地址:https://www.cnblogs.com/enzymii/p/8412177.html