I-country's 题解

这是一道线性dp。。。

然而它并不easy

题目给你一个 n ∗ m n*m nm 的矩阵,让后你可以选一个占 k k k 个格子的凸连通块(中间没有空隙),每个格子有一定的值,问你选择的范围内所有格子的总和最为多少

这道题刚碰到你会感觉没有思路,需要推下样例(或者最好是自己的小数据,因为样例有点水),然后你会找到一个规律,即上下两行之间的关系(或者左右两行的,但这里我以行为例来讲)

显然一个凸多边形可以按行分为若干段连续的区间 [ l , r ] [l,r] [l,r],而且左端点的值先减后增,右端点先增后减

所以我们可以将阶段设置成每一行,一共需要n个阶段

除此之外,在状态转移中,我们还需要记录已经选的格子数,当前的区间,以及左右端点是在递增还是递减,即用 f [ i ] [ j ] [ l ] [ r ] [ x ] [ y ] f[i][j][l][r][x][y] f[i][j][l][r][x][y] 表示当前行,已选格子数,左端点,右端点,左端点递增或减,右端点增或减(0表示向内缩小,1表示向外扩大,即左端点1为减0为增,右端点0为增1为减,现在看来这样貌似在自找麻烦)

然后就一共有4种情况讨论(左右端点分别为递增还是递减)

而要实现方案输出,可以用额外的数组来记录当前状态是由哪个状态转移过来的,最后从结尾一次递归即可

#include
using namespace std;
int n,m,k,l,r,x,y,i,j,a[20][20],b[20][20],f[20][250][20][20][2][2];
int nl[20][250][20][20][2][2],nr[20][250][20][20][2][2];
int nx[20][250][20][20][2][2],ny[20][250][20][20][2][2];
//这里的数组你可以开成一个结构体,因为明显我现在发现这个太烦了(然而这个既然敲着了,那也就不改了)
void init(){
	scanf("%d%d%d",&n,&m,&k);
	memset(f,0,sizeof(f));
	for(i=1;i<=n;++i)
		for(j=1;j<=m;++j){
			scanf("%d",&a[i][j]);
			b[i][j]=b[i][j-1]+a[i][j];//求个前缀和方便求区间和
		}
}
void gg(int p,int L,int R,int X,int Y){//记录当前状态是由哪个状态转移过来的且保证最优方案
	if(p<f[i][j][l][r][x][y]) return;
	f[i][j][l][r][x][y]=p;
	nl[i][j][l][r][x][y]=L;
	nr[i][j][l][r][x][y]=R;
	nx[i][j][l][r][x][y]=X;
	ny[i][j][l][r][x][y]=Y;
}
void work(){
	for(i=1;i<=n;++i)
		for(j=1;j<=k;++j)
			for(l=1;l<=m;++l)
				for(r=l;r<=m;++r){
					int len=r-l+1,p;
					if(len>j) break;//当前行上的格子数大于所要的全部格子数,直接进下一种情况
					p=b[i][r]-b[i][l-1];
					for(x=0;x<=1;++x)
						for(y=0;y<=1;++y)
							gg(p,m,0,x,y);
					x=y=1;//左减右增只能从左减右增状态转移过来
					for(int h=l;h<=r;++h)
						for(int g=h;g<=r;++g)
							gg(f[i-1][j-len][h][g][1][1]+p,h,g,1,1);
					x=1;
					y=0;//左减右减可能从左减右增状态或左减右减状态转移过来
					for(int h=l;h<=r;++h)
						for(int g=r;g<=m;++g){
							gg(f[i-1][j-len][h][g][1][0]+p,h,g,1,0);
							gg(f[i-1][j-len][h][g][1][1]+p,h,g,1,1);
						}
					x=0;
					y=1;//左增右增可能从左减右增状态或左增右增状态转移过来
					for(int h=1;h<=l;++h)
						for(int g=l;g<=r;++g){
							gg(f[i-1][j-len][h][g][0][1]+p,h,g,0,1);
							gg(f[i-1][j-len][h][g][1][1]+p,h,g,1,1);
						}
					x=y=0;//左增右减可能从任何一种状态转移过来
					for(int h=1;h<=l;++h)
						for(int g=r;g<=m;++g){
							gg(f[i-1][j-len][h][g][0][0]+p,h,g,0,0);
							gg(f[i-1][j-len][h][g][0][1]+p,h,g,0,1);
							gg(f[i-1][j-len][h][g][1][0]+p,h,g,1,0);
							gg(f[i-1][j-len][h][g][1][1]+p,h,g,1,1);
						}
				}
}
void hh(int p,int k,int l,int r,int x,int y){
	if(!k) return;
	hh(p-1,k-(r-l+1),nl[p][k][l][r][x][y],nr[p][k][l][r][x][y],nx[p][k][l][r][x][y],ny[p][k][l][r][x][y]);
	for(int h=l;h<=r;++h)
		printf("%d %d\n",p,h);
}
void prin(){
	int p,el,er,ex,ey,sum=0;
	for(int h=1;h<=n;++h)
		for(l=1;l<=m;++l)
			for(r=l;r<=m;++r)
				for(x=0;x<=1;++x)
					for(y=0;y<=1;++y)
						if(sum<f[h][k][l][r][x][y]){
							sum=f[h][k][l][r][x][y];
							p=h; el=l; er=r; ex=x; ey=y;
						}
	printf("Oil : %d\n",sum);
	hh(p,k,el,er,ex,ey);
}
int main(){
	freopen("input.in" ,"r",stdin );
	freopen("output.out","w",stdout);
	init();
	work();
	prin();
	return 0;
}

这代码真tm丑。。。几百年前写的啊。。。有些地方什么鬼啊我自己都块看不懂我在干嘛了。。。果然我tcl(明明是懒到不想改)。。。

emmm,既然nx奆佬来帮忙了,那就改一下吧。。。

你可能感兴趣的:(题解,线性dp)