【BZOJ2310】ParkII 插头DP

【BZOJ2310】ParkII

Description

Hnoi2007-Day1有一道题目 Park:给你一个 m * n 的矩阵,每个矩阵内有个权值V(i,j) (可能为负数),要求找一条回路,使得每个点最多经过一次,并且经过的点权值之和最大,想必大家印象深刻吧. 
无聊的小 C 同学把这个问题稍微改了一下:要求找一条路径,使得每个点最多经过一次,并且点权值之和最大,如果你跟小 C 一样无聊,就麻烦做一下这个题目吧.

Input

第一行 m, n,接下来 m行每行 n 个数即. 
V( i,j)

Output

一个整数表示路径的最大权值之和.

Sample Input

2 3
1 -2 1
1 1 1

Sample Output

5
【数据范围】
30%的数据,n≤6.
100%的数据,m<=100,n ≤ ≤8.
注意:路径上有可能只有一个点.

题解:神奇游乐园的加强版,加入了独立插头,方法是用4进制数表示状态,0-无插头,1-左括号,2-右括号,3-独立插头。然后就进行4*4=16种情况的讨论吧!

注意以下几点即可:

一个独立插头可以与一个括号匹配使得另一个括号变成独立插头;新建独立插头的条件是已有独立插头数<2;一个原本匹配的插头可以自我了断,然后与它匹配的那个插头就变成独立插头了;当两个独立插头匹配时更新答案。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,m,tot,k,ans;
int hs[270000],ref[8320],dp[2][8320],cnt[8320];
inline void upd(int x,int y)
{
	if(dp[k][hs[x]]<y)	dp[k][hs[x]]=y;
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<‘0‘||gc>‘9‘)	{if(gc==‘-‘)	f=-f;	gc=getchar();}
	while(gc>=‘0‘&&gc<=‘9‘)	ret=ret*10+(gc^‘0‘),gc=getchar();
	return ret*f;
}
int main()
{
	n=rd(),m=rd(),ans=0xc0c0c0c0;
	int tmp,u,i,j,v,x,y,tag,p,q,S,T;
	memset(dp[0],0xc0,sizeof(dp[0]));
	for(S=0;S<(1<<(m+m+2));S++)
	{
		for(v=tmp=0,u=0;u<=(m<<1)&&v>=0&&tmp<=2;u+=2)	x=(S>>u)&3,v+=(x==1)-(x==2),tmp+=(x==3);
		if(tmp<=2&&v==0)	ref[++tot]=S,cnt[tot]=tmp,hs[S]=tot;
	}
	dp[0][1]=0;
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=m;j++)
		{
			v=rd(),ans=max(ans,v),k^=1;
			memset(dp[k],0xc0,sizeof(dp[k]));
			dp[k][1]=0;
			for(S=1;S<=tot;S++)
			{
				y=j<<1,x=y-2,p=(ref[S]>>x)&3,q=(ref[S]>>y)&3,tag=dp[k^1][S]+v,T=ref[S]^(p<<x)^(q<<y);
				if(!p&&!q)
				{
					if(cnt[S]<2)
					{
						if(i!=n)	upd(T|(3<<x),tag);
						if(j!=m)	upd(T|(3<<y),tag);
					}
					if(i!=n&&j!=m)	upd(T|(1<<x)|(2<<y),tag);
					upd(T,tag-v);
				}
				if((!p&&q==1)||(!q&&p==1))
				{
					if(i!=n)	upd(T|(1<<x),tag);
					if(j!=m)	upd(T|(1<<y),tag);
					if(cnt[S]<2)
					{
						for(u=y+2,tmp=0;u<=(m<<1)&&tmp>=0;tmp+=((T>>u)&1)-((T>>(u+1))&1),u+=2);
						u-=2;
						upd(T|(1<<u),tag);
					}
				}
				if((!p&&q==2)||(!q&&p==2))
				{
					if(i!=n)	upd(T|(2<<x),tag);
					if(j!=m)	upd(T|(2<<y),tag);
					if(cnt[S]<2)
					{
						for(u=x-2,tmp=0;u>=0&&tmp>=0;tmp+=((T>>(u+1))&1)-((T>>u)&1),u-=2);
						u+=2;
						upd(T|(2<<u),tag);
					}
				}
				if((!p&&q==3)||(!q&&p==3))
				{
					if(i!=n)	upd(T|(3<<x),tag);
					if(j!=m)	upd(T|(3<<y),tag);
					if(!T&&ans<tag)	ans=tag;
				}
				if(p==3&&q==3&&!T&&ans<tag)	ans=tag;
				if(p==2&&q==1)	upd(T,tag);
				if((p==1&&q==3)||(p==3&&q==1))
				{
					for(u=y+2,tmp=0;u<=(m<<1)&&tmp>=0;tmp+=((T>>u)&1)-((T>>(u+1))&1),u+=2);
					u-=2;
					upd(T|(1<<u),tag);
				}
				if((p==2&&q==3)||(p==3&&q==2))
				{
					for(u=x-2,tmp=0;u>=0&&tmp>=0;tmp+=((T>>(u+1))&1)-((T>>u)&1),u-=2);
					u+=2;
					upd(T|(2<<u),tag);
				}
				if(p==1&&q==1)
				{
					for(u=y+2,tmp=0;u<=(m<<1)&&tmp>=0;tmp+=((T>>u)&1)-((T>>(u+1))&1),u+=2);
					u-=2;
					upd(T^(3<<u),tag);
				}
				if(p==2&&q==2)
				{
					for(u=x-2,tmp=0;u>=0&&tmp>=0;tmp+=((T>>(u+1))&1)-((T>>u)&1),u-=2);
					u+=2;
					upd(T^(3<<u),tag);
				}
			}
		}
		for(S=tot;S>=1;S--)
		{
			if(!(ref[S]&3))	dp[k][S]=dp[k][hs[ref[S]>>2]];
			else	dp[k][S]=0xc0c0c0c0;
		}
	}
	printf("%d",ans);
	return 0;
}//2 3 1 -2 1 1 1 1 
时间: 2024-08-28 06:25:39

【BZOJ2310】ParkII 插头DP的相关文章

[入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型…… 每一道题的状态都不一样,并且有不少的分类讨论,让插头DP十分锻炼思维的全面性和严谨性. 下面我们一起来学习插头DP的内容吧! 插头DP主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有很多种,并且一般数据规模较小. 由于棋盘有很特殊的结构,使得它可以与“连通性”有很强的联系,因此插头DP最常见的应用要数

【learning】插头dp

问题描述 一种网格棋盘上的回路(路径也可以)数量统计之类的问题,也可以是求最优值之类的可以考虑dp求解的问题 具体解法 一些必须前置的东西 首先是一些概念的引入: 1.插头:既然是插头dp那肯定要先说说插头是啥,插头其实可以理解为每个格子的路经的走向,有以下几种情况: 2.轮廓线:就是下图中蓝色的那条东西,我们在转移的时候采用状态压缩的方式记录轮廓线的..轮廓,然后一个一个格子来转移 与一般的dp不同,插头dp是基于轮廓线的dp而不是基于格子的dp,这是很重要的一点 具体什么意思呢?就是说我们在

「总结」插头$dp$

集中做完了插头$dp$ 写一下题解. 一开始学的时候还是挺蒙的. 不过后来站在轮廓线$dp$的角度上来看就简单多了. 其实就是一种联通性$dp$,只不过情况比较多而已了. 本来转移方式有两种.逐行和逐格转移. 不过逐行转移因为分类太多所以被舍弃了. 一般的插头$dp$采用逐格转移. 插头表示已经进入当前格子的状态,而并不是将要进入的状态. 状态的表示方式常见的有两种:最小表示法和括号表示法. 括号表示法不如说是广义括号表示法的特殊一种情况,每个插头也就是左右括号就是表示两个相匹配的回路部分,而最

插头dp

对于网格中的dp可以用轮廓线,如果有一些格子不能走就可以用插头dp了. bzoj2331 地板 题目大意:用L型铺地n*m,有一些格子不能铺,求方案数. 思路:fi[i][j][s]表示铺到(i,j),轮廓线状态s,0表示没有插头,1表示插头没拐弯,2表示插头拐弯了,手动枚举转移. 注意:(1)按四进制好写: (2)因为实际状态和四进制的差很多,所以用hash表来存储,防止mle和tle,同时使用滚动数组. #include<iostream> #include<cstdio> #

插头DP学习

队内没人会插头DP,感觉这个不会不行...所以我还是默默去学了一下, 学了一天,感觉会了一点.对于每一行,一共有j+1个插头,如果是多回路类的题目, 比较简单,可以用1表示有插头,0表示没有插头,这样就可以愉快转移了, 对于当前出来的位置(i,j),与它有关的插头有j-1和j 那么我们可以枚举状态经行转移. 对于单回路的问题....只是了解思想,目前还不会写,太笨了=_= poj 2411 Mondriaan's Dream 题意:用1*2小方块组成n*m矩阵有多少种组成方式 思路:我们从上到下

BZOJ 2595 游览计划(插头DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2595 题意:给出一个数字矩阵.求一个连通块包含所有的数字0且连通块内所有数字之和最小. 思路:对于每个格子,是0则必须要选.那么对于不选的格子(i,j)在什么时候可以不选呢?必须同时满足以下两个条件: (1)(i,j)不是0: (2)(i-1,j)不选或者(i-1,j)选了但是轮廓线上还有别的地方与(i-1,j)是一个连通块. int Pre[105][N],op[105][N]; s

【插头dp】CDOJ1690 这是一道比CCCC简单题难的简单题

最裸的插头dp,可参见大白书. #include<cstdio> #include<cstring> using namespace std; #define MOD 1000000007 int f[2][(1<<5)+10],n,m; int main(){ scanf("%d%d",&n,&m); int cur=0; f[0][(1<<m)-1]=1; for(int i=0;i<n;++i){ for(in

POJ 2411 Mondriaan&#39;s Dream ——状压DP 插头DP

[题目分析] 用1*2的牌铺满n*m的格子. 刚开始用到动规想写一个n*m*2^m,写了半天才知道会有重复的情况. So Sad. 然后想到数据范围这么小,爆搜好了.于是把每一种状态对应的转移都搜了出来. 加了点优(gou)化(pi),然后poj上1244ms垫底. 大概的方法就是考虑每一层横着放的情况,剩下的必须竖起来的情况到下一层取反即可. 然后看了 <插头DP-从入门到跳楼> 这篇博客,怒抄插头DP 然后16ms了,自己慢慢YY了一下,写出了风(gou)流(pi)倜(bu)傥(tong)

HDU 4113 Construct the Great Wall(插头dp)

好久没做插头dp的样子,一开始以为这题是插头,状压,插头,状压,插头,状压,插头,状压,无限对又错. 昨天看到的这题. 百度之后发现没有人发题解,hust也没,hdu也没discuss...在acm-icpc信息站发现难得的一篇题解.不过看到是插头二字之后,代码由于风格太不一样就没看了,自己想了好久,想通了.然后就等到今天才码.... 如果把点看成网格,那就可以实现,没有公共点公共边等限定条件,也显然是插头dp的最短单回路的模型.这是本题的一个难点(当时想到这样是因为,题目要求计算最短周长,显然