1. 程式人生 > >【bzoj2310】ParkII 插頭dp

【bzoj2310】ParkII 插頭dp

content 時機 .html 一個 legend highlight scanf 序列 只有一個

題目描述

給你一個 m * n 的矩陣,每個矩陣內有個權值V(i,j) (可能為負數),要求找一條路徑,使得每個點最多經過一次,並且經過的點權值之和最大。

輸入

第一行 m, n,接下來 m行每行 n 個數即 V(i,j)

輸出

一個整數表示路徑的最大權值之和.

樣例輸入

2 3
1 -2 1
1 1 1

樣例輸出

5


題解

插頭dp,神奇遊樂園 的進階版。

陳丹琦的論文 最後有講這種路徑而非回路的做法。

與回路相比,在每個狀態中添加了至多兩個獨立插頭,表示這個插頭沒有與其它插頭相連,獨立存在。在括號序列中既不作為 ‘(‘ 也不作為 ‘)‘ 。

普通轉移與回路一樣,帶獨立插頭的特殊轉移有如下幾種:

左無插頭、上無插頭時,可以新建下或右獨立插頭;
左有獨立插頭、上無插頭時,可以選擇向下或向右傳遞這個獨立插頭;左無插頭、上有獨立插頭時同理;
左有獨立插頭、上有非獨立插頭時,連接兩個插頭,並將上插頭的另一端改為獨立插頭;左有非獨立插頭、上有獨立插頭時同理。

更新答案的時機:

左/上有獨立插頭,其余處均無插頭時更新答案,此時路徑的一個端點為當前格子,另一個端點在獨立插頭的另一端;
左、上均有獨立插頭,其余處均無插頭時更新答案,此時路徑的一個端點為左獨立插頭的另一端,另一個端點為上獨立插頭的另一端。
註意這兩種情況不能更新狀態。

然後就是大量的if判斷吧。。。呵呵呵。。。

註意特判路徑只有一個點的情況(盡管不判斷也能過。。。)

#include <cstdio>
#include <cstring>
int m , a[110][9] , w[263000] , v[8500] , tot , f[110][9][8500];
inline int pos(int a , int i)
{
	return a << (i << 1);
}
inline void gmax(int &a , int b)
{
	a = (a > b ? a : b);
}
void dfs(int p , int c , int k , int now)
{
	if(k > 2 || c < 0 || c > m - p + 1) return;
	if(p > m)
	{
		w[now] = ++tot , v[tot] = now;
		return;
	}
	dfs(p + 1 , c , k , now);
	dfs(p + 1 , c + 1 , k , now + (1 << (p << 1)));
	dfs(p + 1 , c - 1 , k , now + (2 << (p << 1)));
	dfs(p + 1 , c , k + 1 , now + (3 << (p << 1)));
}
inline int l(int v , int p)
{
	int i , c = 0;
	for(i = p ; ~i ; i -- )
	{
		if(((v >> (i << 1)) & 3) == 1) c -- ;
		if(((v >> (i << 1)) & 3) == 2) c ++ ;
		if(!c) return i;
	}
	return -1;
}
inline int r(int v , int p)
{
	int i , c = 0;
	for(i = p ; i <= m ; i ++ )
	{
		if(((v >> (i << 1)) & 3) == 1) c ++ ;
		if(((v >> (i << 1)) & 3) == 2) c -- ;
		if(!c) return i;
	}
	return -1;
}
int main()
{
	int n , i , j , k , s , p , q , ans = -1 << 30;
	scanf("%d%d" , &n , &m);
	dfs(0 , 0 , 0 , 0);
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= m ; j ++ )
			scanf("%d" , &a[i][j]);
	memset(f , 0xc0 , sizeof(f)) , f[1][0][w[0]] = 0;
	for(i = 1 ; i <= n ; i ++ )
	{
		for(j = 1 ; j <= m ; j ++ )
		{
			if(ans < a[i][j]) ans = a[i][j];
			for(k = 1 ; k <= tot ; k ++ )
			{
				p = (v[k] >> ((j - 1) << 1)) & 3 , q = (v[k] >> (j << 1)) & 3 , s = f[i][j - 1][k] + a[i][j];
				if(!p && !q)
				{
					gmax(f[i][j][k] , f[i][j - 1][k]);
					if(j < m) gmax(f[i][j][w[v[k] ^ pos(3 , j)]] , s);
					if(i < n) gmax(f[i][j][w[v[k] ^ pos(3 , j - 1)]] , s);
					if(i < n && j < m) gmax(f[i][j][w[v[k] ^ pos(1 , j - 1) ^ pos(2 , j)]] , s);
				}
				if(!p && q)
				{
					if(j < m) gmax(f[i][j][k] , s);
					if(i < n) gmax(f[i][j][w[v[k] ^ pos(q , j) ^ pos(q , j - 1)]] , s);
					if(q == 1) gmax(f[i][j][w[v[k] ^ pos(1 , j) ^ pos(1 , r(v[k] , j))]] , s);
					if(q == 2) gmax(f[i][j][w[v[k] ^ pos(2 , j) ^ pos(2 , l(v[k] , j))]] , s);
					if(q == 3 && !(v[k] ^ pos(3 , j))) gmax(ans , s);
				}
				if(p && !q)
				{
					if(i < n) gmax(f[i][j][k] , s);
					if(j < m) gmax(f[i][j][w[v[k] ^ pos(p , j - 1) ^ pos(p , j)]] , s);
					if(p == 1) gmax(f[i][j][w[v[k] ^ pos(1 , j - 1) ^ pos(1 , r(v[k] , j - 1))]] , s);
					if(p == 2) gmax(f[i][j][w[v[k] ^ pos(2 , j - 1) ^ pos(2 , l(v[k] , j - 1))]] , s);
					if(p == 3 && !(v[k] ^ pos(3 , j - 1))) gmax(ans , s);
				}
				if(p == 2 && q == 1) gmax(f[i][j][w[v[k] ^ pos(2 , j - 1) ^ pos(1 , j)]] , s);
				if(p == 3 && q == 2) gmax(f[i][j][w[v[k] ^ pos(3 , j - 1) ^ pos(2 , j) ^ pos(2 , l(v[k] , j))]] , s);
				if(p == 1 && q == 3) gmax(f[i][j][w[v[k] ^ pos(1 , j - 1) ^ pos(3 , j) ^ pos(1 , r(v[k] , j - 1))]] , s);
				if((p == 1 || p == 3) && q == 1) gmax(f[i][j][w[v[k] ^ pos(p , j - 1) ^ pos(1 , j) ^ pos(p ^ 2 , r(v[k] , j))]] , s);
				if(p == 2 && (q == 2 || q == 3)) gmax(f[i][j][w[v[k] ^ pos(2 , j - 1) ^ pos(q , j) ^ pos(q ^ 1 , l(v[k] , j - 1))]] , s);
				if(p == 3 && q == 3 && !(v[k] ^ pos(3 , j - 1) ^ pos(3 , j))) gmax(ans , s);
			}
		}
		for(j = 1 ; j <= tot ; j ++ )
			if(!(v[j] & 3))
				f[i + 1][0][j] = f[i][m][w[v[j] >> 2]];
	}
	printf("%d\n" , ans);
	return 0;
}

【bzoj2310】ParkII 插頭dp