1. 程式人生 > >方格取數問題(網路流24題之一)

方格取數問題(網路流24題之一)

                                                                                   方格取數

題目描述

在一個有 m*n 個方格的棋盤中,每個方格中有一個正整數。現要從方格中取數,使任意 2 個數所在方格沒有公共邊,且取出的數的總和最大。試設計一個滿足要求的取數演算法。對於給定的方格棋盤,按照取數要求程式設計找出總和最大的數。

輸入輸出格式

輸入格式:

第 1 行有 2 個正整數 m 和 n,分別表示棋盤的行數和列數。接下來的 m 行,每行有 n 個正整數,表示棋盤方格中的數。

輸出格式:

程式執行結束時,將取數的最大總和輸出

題目連結

這道題的題意還是比較好理解的,就是不能取相鄰的兩個數,使所有取出來的數的總和最大。。

嗯,先不要急著想搜尋、暴力或貪心(雖然有可能可以過,如果可以,歡迎指教),這題可是出自網路流24題的呀。

所以肯定是用網路流    我們也可以用網路流來做。

首先,也是最關鍵的,這題如何建圖?

我們可以這樣想:

把格子染成黑白兩色,形如下圖:  有點醜。。好吧,不止一點

其實黑白兩色也可以反過來,不過這不是重點。

我們把每個格標個號:

然後我們可以把格子抽象成一個點,然後分開:

最後就是連邊:

左邊那個藍藍的是源點,然後那個白色的圈是匯點。

從源點連向黑點的邊的權值(容量)是那個黑點的值,然後從白點連向匯點的邊也是權值。

最後中間那一大坨是。。。。嗯,邊的兩邊的點時候不能同時選的(因為有公共邊),邊權是INF(儘量大)。

我們要求的是什麼呢,沒錯,選出來的所有點的權值最大…… 這TMD和這圖有什麼關係啊?

別心急,這題應該是要求最大點權獨立集(注意:是點權),就等於所有點的值減去最小點權覆蓋集,就是減去最小割(最大流),然後我們就可以開心地寫程式碼了。

可能有些同學並不理解最後一句話是什麼意思(其實一開始我也不理解)。

其實是這樣的,我們相當與把點權轉換為了邊權,我們的目的是斷開一些邊,使得沒有路徑從源點到達匯點(為了滿足題目的限制條件嗎),然後我們要使斷開的邊的權值之和最小(斷開的邊就相當於是不選那個點,就是剩下的邊權之和最大->這就是我們要求的答案嘛),乍一看,這不就是最小割嗎?因為最小割=最大流,所以我們可以跑一遍最大流,然後用總的邊權減去最大流就是我們的答案了,是不是很棒棒。

總結:做完這題,我對網路流的理解又加深了一層。我們要巧妙的利用網路流的建模技巧(這也是網路流的精髓),把問題轉換為可以用網路流解決的問題,要多刷題,刷好題,才能掌握技巧。

上程式碼:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=1000005;
const int MAXM=1000005;
int d[MAXN],n,m,p[MAXN],eid,S,T,x[MAXN],y[MAXN],z[MAXN],a[105][105],sz,sum;
struct A{  //灰常正常的最大流(Dinic)
	int v,c,next;
}e[MAXM];
void init(){
	memset(p,-1,sizeof(p));
	eid=0;
}
void add(int u,int v,int c){
	e[eid].v=v;
	e[eid].c=c;
	e[eid].next=p[u];
	p[u]=eid++;
}
void insert(int u,int v,int c){
	add(u,v,c);
	add(v,u,0);
}
int bfs(){
	memset(d,-1,sizeof(d));
	queue<int>q;
	d[S]=0;
	q.push(S);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=p[u];i!=-1;i=e[i].next){
			int v=e[i].v;
			if(e[i].c>0&&d[v]==-1){
				d[v]=d[u]+1;
				q.push(v);	
			}
		}
	}
	return (d[T]!=-1);
}
int dfs(int u,int flow){ 
	if(u==T) return flow;
	int ret=0;
	for(int i=p[u];i!=-1;i=e[i].next){
		int v=e[i].v;
		if(e[i].c>0&&d[v]==d[u]+1){
			int tmp=dfs(v,min(flow,e[i].c));
			e[i].c-=tmp;
			e[i^1].c+=tmp;
			flow-=tmp;
			ret+=tmp;
			if(!flow) break;
		}
	}
	if(!ret) d[u]=-1;
	return ret;
}
int Dinic(){
	int ret=0;
	while(bfs()){
		ret+=dfs(S,INF);
	}
	return ret;
}
int main(){    //以下開始碼風突變(中了yjq的膜法)
	init();
	scanf("%d%d", &m, &n);
	S=0;T=n*m+1;//建立源點和匯點
	for(int i = 1; i <= m; i++){
		for(int j = 1; j <= n; j++){
			scanf("%d", &a[i][j]);
			sum += a[i][j];
			sz ++;
			if((i + j) % 2){
				insert(S, sz, a[i][j]);//連向源點
				if(j < n) insert(sz, sz + 1, INF);//把有限制條件的連起來,邊權注意要儘量大
				if(j > 1) insert(sz, sz - 1, INF);
				if(i < m) insert(sz, sz + n, INF);
				if(i > 1) insert(sz, sz - n, INF);
			} else {
				insert(sz,T,a[i][j]);//連向匯點
			}
		}
	} 
	printf("%d",sum - Dinic());//總的邊權 - 最大流(最小割)
	return 0;
}