Poj2430:Lazy Cows 解题思路&代码

准备不到一个月后的保研机试,最近开始重新拾起一些算法题练手。记得高中竞赛那会就最喜欢动规题了,也是我个人最喜欢和最擅长的算法,没想到5年过去了居然还是没怎么变偷笑做起来很有兴趣也很顺手。我认为动规的核心在于“状态设计”,基本上拿到问题,只要设计出了合理的状态,那么转移也就不难写(因为状态设计是合理的),然后问题也就解决了大半。对于一些较复杂的动规问题,合理的状态设计有难度,但也很有“美感”。

这道题的大意是有一个2*B(1=<B<=15000000)的大矩阵,该矩阵中有N(N<=1000)头奶牛(即大矩阵中有指定的N个点)。现在想用K个小矩阵去覆盖这全部N个点,求在保证N个点被完全覆盖的情况下K个小矩阵的面积的最小值。

这道题有一点小难度,最起码比那些一眼就能看穿的动规水题更有质量一些。

B很大,而N很小,离散化的处理方式是很自然的。然后用动规来做。具体的思路都写在代码注释里了。这里就直接贴代码了。

// Poj 2430 Lazy Cows
// By Victor Li
// 2016-08-28

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <string.h>
using namespace std;

struct coordinate{
	int x;
	int label;
	int sum;
};

bool coordinateCompare(const coordinate & a, const coordinate & b){
	return ( a.x<b.x || (a.x==b.x && a.label<b.label) );
}

int f[1001][1001][4];

int min_(int x,int y){
	if (x<y) return x;
		else return y;
}

int main(){

	int N, K, B;
	scanf("%d%d%d",&N,&K,&B);
	
	//读入奶牛序列信息并排序(列号为第一关键字,行号为第二关键字)
	coordinate * Co = (coordinate *)calloc(N,sizeof(coordinate));
	for (int i=0;i<N;i++) scanf("%d%d",&Co[i].label,&Co[i].x);
	sort(Co,Co+N,coordinateCompare);
	
	coordinate * MerCo = (coordinate *)calloc(N,sizeof(coordinate));
	int i = 0 , N_ = 0;

	//排序后,将序列中处在同一列上的两头奶牛合并(Merge)为一个点,方便后面DP计算过程
	while (i<N){
		
		if (i<N-1 && Co[i].x == Co[i+1].x){
			MerCo[N_].x = Co[i].x;
			//如果一列上有两头奶牛,label设为0
			MerCo[N_].label = 0;						
			if (N_ == 0) MerCo[N_].sum = 2;
				else MerCo[N_].sum = MerCo[N_-1].sum + 2;
			N_++;
			i+=2;
		}	else {
			MerCo[N_].x = Co[i].x;
			//一列上只有一头奶牛,label设为它所在的行号(1或2)
			MerCo[N_].label = Co[i].label;
			if (N_ == 0) MerCo[N_].sum = 1;
				else MerCo[N_].sum = MerCo[N_-1].sum + 1;
			N_++;
			i+=1;
		}
	}
	free(Co);

	/*
	状态设计简单解释:
	f[i][j]表示用j个矩阵覆盖前i列的所有奶牛,覆盖面积最小的方案
	但是,如果我们仅将状态设计f[i][j],却发现很难写出状态转移方程
	因为这样的状态设计不能满足“无后效性”的原则
	多加一维k,k=0..3,分别对应当前状态最后一列的4种不同的形态
	f[i][j][0]:
	-----
		 |          最后一列处是一个 len*2的矩阵的末尾(len是多少不重要,不用记录)
		 |
	-----
	f[i][j][1]:
	-----
		 |          最后一列的第一行是一个len*1的矩阵的末尾,第二行处为空,无矩阵覆盖
	-----
			   
	-----
	f[i][j][2]:		与f[i][j][1]类似,是第一行无覆盖,第二行有覆盖

	f[i][j][3]:
	-----
		 |			最后一列处为两个宽度为1的矩阵的末尾
	-----
		 |
	-----

	一开始做时,我没设计k=3的情况,导致Wrong Answer
	没有k=3的情况下,一个典型的错例:
		3 2 100
		1 1
		2 50
		1 100
	这个例子的最优方案就是两个宽度为1的矩阵,如果没有k=3,原来的状态设计和转移方案无法表达这种方案
	*/


	//动态规划边界初值
	memset(f,127,sizeof(f));
	if (MerCo[0].label == 0) {
		f[0][1][0] = 2;
		if (K>1) f[0][2][3] = 2;
	}	else{
		f[0][1][0] = 2;
		f[0][1][ MerCo[0].label ] = 1;
		if (K>1) f[0][2][3] = 2;
	}

	//转移计算:i,j大循环,循环体内依次计算f[i][j][0] ~ f[i][j][3]
	//结合各个状态的实际含义,仔细地考察各种转化情况的可能性,不要有遗漏即可
	//以下转移过程结合代码即可理解其具体含义,这里就不多做解释了
	for (i=1;i<N_;i++)
		for (int j=1;j<=min_(K,MerCo[i].sum);j++){


			f[i][j][0] = min_(f[i][j][0], f[i-1][j][0] + (MerCo[i].x-MerCo[i-1].x)*2 );
			for (int k=0;k<4;k++)
				f[i][j][0] = min_(f[i][j][0] , f[i-1][j-1][k]+2);

			if (MerCo[i].label > 0) {
				int l = MerCo[i].label;
				f[i][j][l] = min_(f[i][j][l], f[i-1][j][l] + (MerCo[i].x-MerCo[i-1].x) );
				f[i][j][l] = min_(f[i][j][l], f[i-1][j][3] + (MerCo[i].x-MerCo[i-1].x) );
				for (int k=0;k<4;k++)
					f[i][j][l] = min_(f[i][j][l] , f[i-1][j-1][k]+1);

			}

			f[i][j][3] = min_(f[i][j][3], f[i-1][j][3] + (MerCo[i].x-MerCo[i-1].x)*2 );
			if (MerCo[i].label == 0 && j>1) {
				for (int k=0;k<4;k++)
					f[i][j][3] = min_(f[i][j][3], f[i-1][j-2][k]+ 2 );
			}
			for (int k=1;k<4;k++){
				f[i][j][3] = min_(f[i][j][3], f[i-1][j-1][k] + (MerCo[i].x-MerCo[i-1].x) + 1 );
			}
				

		}

	free(MerCo);

	//答案为f[N_-1][K][0..3]中的最小值
	if (f[N_-1][K][0]<f[N_-1][K][1] && f[N_-1][K][0]<f[N_-1][K][2] && f[N_-1][K][0]<f[N_-1][K][3] ) printf("%d\n",f[N_-1][K][0]);
		else if (f[N_-1][K][1]<f[N_-1][K][2] && f[N_-1][K][1]<f[N_-1][K][3]) printf("%d\n",f[N_-1][K][1]);
			else if (f[N_-1][K][2]<f[N_-1][K][3]) printf("%d\n",f[N_-1][K][2]);
				else printf("%d\n",f[N_-1][K][3]);

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值