bzoj 1187: [HNOI2007]神奇游乐园 插头dp

       一道最裸的插头dp,然而还是调了很久。。代码2.6k+调试语句1.5k。。不过这次代码很多可以不需要的但是这种题目关键还是要思路清晰。。。

       然而窝用了3进制作死时间被爆出翔。。。。

       令dp[i][j][k]表示在第i行第j列状态为k时的最大收入,其中k表示插头的状态,即一个括号序列。令上面一段连续的在轮廓线上的左端点为'(',右端点为')',或者也可以看成是从左端点出发在轮廓线上面走过某一段连续的路径之后再右端点回到了轮廓线。那么令'('=1,')'=2,没有插头为0,就可以用一个三进制来压缩状态了。

       那么可以用除法和取模快速得到某一位,在轮廓线的转折处有3*3=9中情况分类讨论。

       1.左边和上面都没有插头,那么这一格可以选择不放,或者向下放一个'(',同时向右放一个')';

       2.左边或者上面有一个'('插头,那么这一格只能向下放一个'('或者向右放一个'(';

       3.左边或者上面有一个')'插头,同上;

       4.左边和上面都有'('插头,这种情况比较复杂,显然是需要把两个插头连起来的,但是状态会有变化,如图(有点丑QAQ):

bzoj 1187: [HNOI2007]神奇游乐园 插头dp_第1张图片

       可以发现此时上面的'('插头对应的')'插头变成了'('插头。。。于是为了O(1)转移我们需要预处理每一个状态的每一位和它对应的括号的位置。然后修改一下那个位置的状态即可。

       5.上面和左边都是')'插头,同上;

       6.左边是'('插头,上面是')'插头,此时应该判断其它地方有没有插头,如果没有插头就更新答案。不进行转移;

       7.左边是')'插头,上面是'('插头,那么直接连上即可。

       然后就是考验细心程度的时候了。。。

AC代码如下:

#include
#include
#include
#define inf 1000000000
using namespace std;

int m,n,a[105][7],lp[2187][7],rp[2187][7],f[105][7][2187],c[15]; bool bo[2187];
bool check(int t){
	int i,x,s=0;
	for (i=0; i<=n; i++){
		x=t/c[i]%3;
		if (x==1) s++; else if (x==2) s--;
		if (s<0) return 0;
	}
	return !s;
}
int calc(int t){
	int i,x,ans=0,s=0;
	for (i=0; i1) return -inf;
	}
	return ans;
}
int findl(int t,int p){
	int i,x,s=1;
	for (i=p-1; i>=0; i--){
		x=t/c[i]%3;
		if (x==1) s--; else if (x==2) s++;
		if (!s) return i;
	}
	return 0;
}
int findr(int t,int p){
	int i,x,s=1;
	for (i=p+1; i<=n; i++){
		x=t/c[i]%3;
		if (x==1) s++; else if (x==2) s--;
		if (!s) return i;
	}
	return n;
}
void up(int &x,int y){ if (x


by lych

2016.4.14

你可能感兴趣的:(bzoj)