I-country

Title
在 N* M 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮廓是凸的,如书中图片所示),使这个连通块中的格子的权值和最大。求出这个最大的权值和,并给出连通块的具体方案。N,M≤15,K≤225。

输入格式
On the first line of input there are 3 integer numbers N,M,K (1<=N,M<=15, 0<=K<=N*M). Next N lines contains M integers each, which are the number of oil resource on that square. Each of this numbers lies in range of 0 to 1000.

输出格式
On the first line of output, write string “Oil : X”, where integer number X — the maximal number of oil which can be controlled by A-country. Next you should output K pairs of numbers — coordinates of the squares which will be occupied by A-country. The first coordinate is number of row (top to bottom, starting from 1), second is number of column (left to right, starting from 1).

样例输入
2 3 4
10 20 30
40 2 3

样例输出
Oil : 100
1 1
1 2
1 3
2 1

思路:任何一个凸连通块可以划分成连续的若干行,每行的左端点列号先递减、后增加,右端点列号先递增后递减。
我们依次考虑从N*M矩阵的每一行中选择哪些格子来构成所求的凸连通块。我们关注的信息有:
1、当前已处理完的行数。
2、当前行已经选出的格子数。
3、当前行已选格子的左端位置,为了确定下一行左端点的范围,以满足单调性。
4、当前行已选格子的右端位置,为了确定下一行右端点的范围,以满足单调性。
5、当前行左侧轮廓的单调性类型,列号是该递增还是递减
6、当前行右侧轮廓的单调性类型,列号是该递增还是递减
也就是状态表示需要6个信息:
F[i,j,l,r,x,y]表示前i行选了j个格子,其中第i行选择了第l到r个格子(若不选择均为0),左边界的单调类型为x,右边界的单调类型y(0表示递增,1表示递减)时,能够构成的凸连通块的最大权值和。
状态转移方程:
1、左边界列号递减,右边界列号递增(两个边界都处于扩张状态)。
在这里插入图片描述
2、左、右边界列号都是递减(左边界扩张,右边界收缩)。
在这里插入图片描述
3、左、右边界列号都递增(左边界收缩,右边界扩张)。
在这里插入图片描述
4、左边界递增,右边界递减(两个边界都处于搜索状态)
在这里插入图片描述
初态:F[i,0,0,0,1,0]=0
目标:max{F[i,k,l,r,x,y]}
时间复杂度:O(NM4K)=O(N2M5
本题还需要输出方案。在动态规划问题需要给出方案是,通常做法是额外使用一些与DP状态大小相同的数组记录下来每个状态的“最优解”是从何处转移而来的。最终,在DP求出最优解后,通过一次递归,沿着记录的每一步“转移来源”回到初态,即可得到从初态到最优解的转移路径,也就是具体方案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[16][226][16][16][2][2],a[16][16],b[16][16],n,m,t,i,j,k,l,r,x,y,p,q,ans,now,ai,al,ar,ax,ay;
char cl[16][226][16][16][2][2],cr[16][226][16][16][2][2],dx[16][226][16][16][2][2],dy[16][226][16][16][2][2];

inline void update(int dat,int L,int R,int X,int Y)
{
	if(dat<f[i][j][l][r][x][y]) return;
	f[i][j][l][r][x][y]=dat;
	cl[i][j][l][r][x][y]=L,cr[i][j][l][r][x][y]=R;
	dx[i][j][l][r][x][y]=X,dy[i][j][l][r][x][y]=Y; 
}

void print(int i,int j,int l,int r,int x,int y)
{
	if(!j) return;
	print(i-1,j-(r-l+1),cl[i][j][l][r][x][y],cr[i][j][l][r][x][y],dx[i][j][l][r][x][y],dy[i][j][l][r][x][y]);
	for(j=l;j<=r;j++) printf("%d %d\n",i,j);
}

int main()
{
	cin>>n>>m>>t;
	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];
		}
	memset(f,0xcf,sizeof(f));
	for(i=1;i<=n;i++)
		for(j=1;j<=t;j++)
			for(l=1;l<=m;l++)
				for(r=l;r<=m;r++)
				{
					if((k=r-l+1)>j) break;
					now=b[i][r]-b[i][l-1];
					for(x=0;x<2;x++)
						for(y=0;y<2;y++) update(now,m,0,x,y);
					x=y=1;
					for(p=l;p<=r;p++)
						for(q=p;q<=r;q++)
							update(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
					x=y=0;
					for(p=1;p<=l;p++)
						for(q=r;q<=m;q++)
						{
							update(f[i-1][j-k][p][q][0][0]+now,p,q,0,0);
							update(f[i-1][j-k][p][q][1][0]+now,p,q,1,0);
							update(f[i-1][j-k][p][q][0][1]+now,p,q,0,1);
							update(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
						} 
					x=1,y=0;
					for(p=l;p<=r;p++)
						for(q=r;q<=m;q++)
						{
							update(f[i-1][j-k][p][q][1][0]+now,p,q,1,0);
							update(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
						}
					x=0,y=1;
					for(p=1;p<=l;p++)
						for(q=l;q<=r;q++)
						{
							update(f[i-1][j-k][p][q][0][1]+now,p,q,0,1);
							update(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
						}
				}
	for(i=1;i<=n;i++)
		for(l=1;l<=m;l++)
			for(r=l;r<=m;r++)
				for(x=0;x<2;x++)
					for(y=0;y<2;y++)
						if(ans<f[i][t][l][r][x][y]) 
						{
							ans=f[i][t][l][r][x][y];
							ai=i,al=l,ar=r,ax=x,ay=y;
						}
	cout<<"Oil : "<<ans<<endl;
	print(ai,t,al,ar,ax,ay);
	return 0;
}
/*
2 3 4
10 20 30
40 2 3
*/ 

对于当正确的输出结果不唯一的时候,我们就需要使用自定义校验器们,也就是我们常说的Special Judge。

//spj.cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std; 
//一些定义
const int ACCEPT = 0;
const int WRONG_ANSWER = 1;
//fstd 标准输出 fout 选手输出 fin 标准输入
FILE *fstd,*fout,*fin;
int n, m, k, a[20][20], v[20][20], ans, val;

//执行比较操作。
bool DoCompare(){
    fscanf(fin, "%d%d%d", &n, &m, &k);
    for (int i = 1; i <= n; i++)
    	for (int j = 1; j <= m; j++) fscanf(fin, "%d", &a[i][j]);
    fscanf(fstd, "%*s%*s%d", &ans);
    fscanf(fout, "%*s%*s%d", &val);
    // 答案不对 
    if (val != ans) return false;
    for (int i = 1; i <= k; i++) {
    	int x, y; fscanf(fout, "%d%d", &x, &y);
    	// 格子越界或者有重复 
    	if (x < 1 || y < 1 || x > n || y > m || v[x][y]) return false;
    	v[x][y] = 1;
    	val -= a[x][y];
    }
    // 输出的格子加起来不等于答案 
    if (val) return false;
    // 检查凸性 
    for (int i = 1; i <= n; i++) {
    	int cnt = 0, l = m, r = 1;
    	for (int j = 1; j <= m; j++)
    		if (v[i][j]) cnt++, l = min(l, j), r = max(r, j);
    	if (cnt == 0) continue;
    	// 输出的格子在一行里不连续 
    	if (r - l + 1 != cnt) return false;
    }
    for (int j = 1; j <= m; j++) {
    	int cnt = 0, l = n, r = 1;
    	for (int i = 1; i <= n; i++)
    		if (v[i][j]) cnt++, l = min(l, i), r = max(r, i);
    	if (cnt == 0) continue;
    	// 输出的格子在一列里不连续 
    	if (r - l + 1 != cnt) return false;
	}
	return true;
}

int main(int argc, char* argv[])
{
    if(argc!=4){
        printf("参数不足 %d",argc);
        return -1;
    }

    //打开文件
    if(NULL==(fstd=fopen(argv[1],"r"))){
        return -1;
    }
    if(NULL==(fout=fopen(argv[2],"r"))){
        return -1;
    }
    if(NULL==(fin=fopen(argv[3],"r"))){
        return -1;
    }

    if(DoCompare()){
        return ACCEPT;
    }else{
        return WRONG_ANSWER;
    }
}

在测试时,常需要测试数据,下面是一个随机产生测试数据的程序。

#include<iostream>
//random.cpp
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
using namespace std;
int n, m, k;
int main() {
	srand((unsigned)time(0));
	cin >> n >> m >> k;
	cout << n << ' ' << m << ' ' << k << endl;
	for (int i = 1; i <= n; i++) {
		for (int j =1; j <= m; j++)
			printf("%d ", rand() % 1001);
		puts("");
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值