sgu167:I-country

题意:
在一个n*m(n,m<=15)的网格中,每个格子有一个值,现在从网格中取出k(k<=n*m)个,
      保证在选中的格子中从任意一个格子去另外的所有格子(路径上的点也要是选出的格子)最多只用到四种(上,下,左,右)操作中的两种,
      并使得到的值最大。输出该值和选中的格子坐标。
分析:
因为路径上的所有点必须在选出的格子内,且只能有两个方向,所以所有选出来的点必定可以组成一个凸多边形。
设f[i][p][q][l][r][s],i为当前1到i层,选p到q的所有数,左(l)右(r)边是否可以继续延伸(0可以,1不行),已经选了s个数的最大权值和。
根据我们只能构成凸多边形,假定f[i][p][q][l][r][s]由f[i'][p'][q'][l'][r'][s']转移来,如果r'为1,r也为1;如果r'为0,但q<q',r为1;否则为0.
状态数O(N^2M^3),时间复杂度O(N^2M^5),看似会超时,但实际上能够转移的状态没那么多。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 16, MAXM = 16, MAXK = 226;
int f[MAXN][MAXM][MAXM][2][2][MAXK]; 
int step[MAXN][MAXM][MAXM][2][2][MAXK], ansflag;
int er[7] = {0, 16, 256, 4096, 8192, 16384, 4194304};
int pre[MAXN][MAXM];
int n, m, k, map[MAXN][MAXN];
int oil = 0;

void makestep(int &t, int i, int p, int q, int l, int r, int s)
{
	t = i+p*er[1]+q*er[2]+l*er[3]+r*er[4]+s*er[5];
}

void deal(int _i, int _j, int _k, int _l, int _r, int _s)
{
	for(int p = _l?_j:1, l, r, tmp; p <= _k; ++p)
		for(int q = max(p, _j), mm = _r?_k:m; q <= mm && q-p+1+_s <= k; ++q)
		{
			l = _l, r = _r;
			if(!l && p > _j) l = 1;
			if(!r && q < _k) r = 1;
			tmp = f[_i][_j][_k][_l][_r][_s]+pre[_i+1][q]-pre[_i+1][p-1];
			if(tmp > f[_i+1][p][q][l][r][_s+q-p+1])
			{
				f[_i+1][p][q][l][r][_s+q-p+1] = tmp;
				makestep(step[_i+1][p][q][l][r][_s+q-p+1], _i, _j, _k, _l, _r, _s);
				if(_s+q-p+1 == k && tmp > oil) 
				{	
					oil = tmp;	
					makestep(ansflag, _i+1, p, q, l, r, _s+q-p+1);
				}
			}
		}
}

void find_path(int cur)
{
	int sta = cur;
	if(sta%er[1]) find_path(step[sta%er[1]][sta%er[2]/er[1]][sta%er[3]/er[2]][sta%er[4]/er[3]][sta%er[5]/er[4]][sta%er[6]/er[5]]);
	else return;
	for(int i = sta%er[2]/er[1], lim = sta%er[3]/er[2]; i <= lim; ++i)
		cout << (sta%er[1]) << ' ' << i << endl;	
}

int main()
{
	cin >> n >> m >> k;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
		{
			cin >> map[i][j];
			pre[i][j] = pre[i][j-1]+map[i][j];
		}
	
	memset(f[1], 0xfffff, sizeof(f[1]));
	for(int p = 1; p <= n; ++p)
		for(int i = 1, l, r; i <= m; ++i)
			for(int j = i; j <= m && j-i+1 <= k; ++j)
			{
				l = r = 1;
				if(i > 1) l = 0;
				if(j < m) r = 0;
				f[p][i][j][l][r][j-i+1] = pre[p][j]-pre[p][i-1];
				if(j-i+1 == k && f[p][i][j][l][r][j-i+1] > oil)
				{
					oil = f[p][i][j][l][r][j-i+1];
					makestep(ansflag, p, i, j, l, r, j-i+1);
				}
			}
	
	for(int i = 1; i < n; ++i)
		for(int p = 1; p <= m; ++p)
			for(int q = p; q <= m; ++q)
				for(int l = q-p+1; l <= k; ++l)
				{
					if(f[i][p][q][0][0][l] != -1) deal(i, p, q, 0, 0, l);
					if(f[i][p][q][0][1][l] != -1) deal(i, p, q, 0, 1, l);
					if(f[i][p][q][1][0][l] != -1) deal(i, p, q, 1, 0, l);			
					if(f[i][p][q][1][1][l] != -1) deal(i, p, q, 1, 1, l);					
				}
				
	cout << "Oil : " << oil << endl;
	find_path(ansflag);	

	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值