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):


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

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

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

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

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

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#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; i<n; i++){
		x=t/c[i]%3;
		if (x==1){ s++; ans+=a[1][i+1]; }
		else if (x==2){ s--; ans+=a[1][i+1]; }
		else if (s) ans+=a[1][i+1];
		if (s>1) 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<y) x=y; }
int main(){
	scanf("%d%d",&m,&n); int i,j,k,x,u,v,tmp;
	for (i=1; i<=m; i++)
		for (j=1; j<=n; j++) scanf("%d",&a[i][j]);
	c[0]=1; for (i=1; i<=n+1; i++) c[i]=3*c[i-1];
	memset(f,192,sizeof(f));
	for (i=0; i<c[n+1]; i++) if (check(i)){
		bo[i]=1;
		for (j=0; j<=n; j++){
			k=i/c[j]%3;
			if (k==2) lp[i][j]=findl(i,j); else
			if (k==1) rp[i][j]=findr(i,j);
		}
		if (!(i/c[n])) f[1][n][i]=calc(i);
	}
	int ans=-inf;
	for (i=2; i<=m; i++){
		for (j=0; j<c[n+1]; j++) if (bo[j] && !(j/c[n])){
			u=j%3; x=(j-u)*3; tmp=f[i-1][n][j];
			if (!u){
				up(f[i][1][x+7],tmp+a[i][1]);
				up(f[i][1][x],tmp);
			} else if (u==1){
				up(f[i][1][x+1],tmp+a[i][1]);
				up(f[i][1][x+3],tmp+a[i][1]);
			}
		}
		for (j=2; j<=n; j++)
			for (k=0; k<c[n+1]; k++) if (bo[k]){
				u=k/c[j-1]%3; v=k/c[j]%3;
				x=k-u*c[j-1]-v*c[j]; tmp=f[i][j-1][k];
				if (!u && !v){
					up(f[i][j][x],tmp);
					up(f[i][j][x+c[j-1]+2*c[j]],tmp+a[i][j]);
				} else if (!u && v==1 || u==1 && !v){
					up(f[i][j][x+c[j-1]],tmp+a[i][j]);
					up(f[i][j][x+c[j]],tmp+a[i][j]);
				} else if (!u && v==2 || u==2 && !v){
					up(f[i][j][x+2*c[j-1]],tmp+a[i][j]);
					up(f[i][j][x+2*c[j]],tmp+a[i][j]);
				} else if (u==1 && v==1){
					up(f[i][j][x-c[rp[k][j]]],tmp+a[i][j]);
				} else if (u==2 && v==2){
					up(f[i][j][x+c[lp[k][j-1]]],tmp+a[i][j]);
				} else if (u==1 && v==2){
					if (!x) up(ans,tmp+a[i][j]);
				} else{
					up(f[i][j][x],tmp+a[i][j]);
				}
			}
	}
	printf("%d\n",ans);
	return 0;
}


by lych

2016.4.14

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值