1. 程式人生 > >【題解】CH5104 I-country 線性DP

【題解】CH5104 I-country 線性DP

題目連結

描述

在 N*M 的矩陣中,每個格子有一個權值,要求尋找一個包含 K 個格子的凸連通塊(連通塊中間沒有空缺,並且輪廓是凸的,如書中圖片所示),使這個連通塊中的格子的權值和最大。求出這個最大的權值和,並給出連通塊的具體方案。本題有SPJ,輸出任意一種方案即可。N,M≤15,K≤225。

According to top-secret A-country plans, I-country is divided into N*M equal squares, each square contains some oil resources. They want to occupy all the territory of I-country, but the UN (United Nations) will allow them to occupy only K squares. Of course, A-country want to get control over as many oil as possible, but, they will have to guard all their territory. So, they need their territory to be easy-controlled, i.e. from any square to any it must be possible to get moving only along two directions (selected from the next list: left, right, up, down; for different squares pairs of directions may differ). You are to write a program, which determinies, what squares will be occupyed by A-country. If there are several solutions, you may output any.

輸入格式

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

本題校驗器(SPJ)

#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;
    }
}

F[i,j,l,r,x,y]F[i,j,l,r,x,y] 表示前 ii 行選擇了 jj 個格子,其中第 ii 行選擇了第 llrr 個格子,左邊界狀態為 xx,右邊界狀態為 yyx=0x=0 為收縮,x=1x=1 為擴張,yy同理),能構成的凸連通塊的最大權值和。 狀態轉移方程: 1.左邊界擴張,右邊界擴張: F[i,j,l,r,1,1]=p=lrA[i,p]+{F[i1,0,m,0,1,1],j=rl+1&gt;0maxlpqrF[i1,j(rl+1),p,q,1,1],j&gt;rl+1&gt;0F[i,j,l,r,1,1]=\sum_{p=l}^rA[i,p]+\begin{cases}F[i-1,0,m,0,1,1],\quad j=r-l+1&gt;0\\ \max\limits_{l\leq p\leq q\leq r}{F[i-1,j-(r-l+1),p,q,1,1]},\quad j&gt;r-l+1&gt;0\end{cases} 2.左邊界擴張,右邊界收縮: F[i,j,l,r,1,1]=p=lrA[i,p]+maxlprq{max0y1{F[i1,j,(rl+1),p,q,1,y]}}F[i,j,l,r,1,1]=\sum_{p=l}^rA[i,p]+\max\limits_{l\leq p\leq r\leq q}\Big\{\max\limits_{0\leq y\leq1}\{F[i-1,j,(r-l+1),p,q,1,y]\}\Big\} 3.左邊界收縮,右邊界擴張: F[i,j,l,r,0,1]=p=lrA[i,p]+maxplqr{max0x1{F[i1,j(rl+1),p,q,x,1]}}F[i,j,l,r,0,1]=\sum_{p=l}^rA[i,p]+\max\limits_{p\leq l\leq q\leq r}\Big\{\max\limits_{0\leq x\leq1}\{F[i-1,j-(r-l+1),p,q,x,1]\}\Big\} 4.左、右邊界都收縮: F[i,j,l,r,0,0]=p=lrA[i,p]+maxplrq{max0x1{max0y1{F[i1,j(rl+1),p,q,x,y]}}}F[i,j,l,r,0,0]=\sum_{p=l}^rA[i,p]+\max\limits_{p\leq l\leq r\leq q}\Bigg\{\max\limits_{0\leq x\leq1}\Big\{\max\limits_{0\leq y\leq 1}\{F[i-1,j-(r-l+1),p,q,x,y]\}\Big\}\Bigg\} 初態:F[i,0,0,0,1,1]=0F[i,0,0,0,1,1]=0,目標:max{F[i,K,l,r,x,y]}\max\{F[i,K,l,r,x,y]\},時間複雜度:O(NM4K)=O(N2M5)O(NM^4K)=O(N^2M^5) 還要記錄每個狀態的“最優解”是從何處轉移而來的。

#include<cstdio>
#include<cstring>
#define _rep(i,a,b) for(i=(a);i<=(b);i++)
#define _for(i,a,b) for(i=(a);i<(b);i++)
int n,m,K,now,l,r,x,y,i,j,p,q,k,si,sl,sr,sx,sy,ans;
int a[16][16],sum[16][16],f[16][226][16][16][2][2];
//f[i][j][l][r][x][y]表示前i行選擇j個格子嗎,其中第i行選擇了第l到r個格子(m到0不選——
//x=0表示左邊界收縮,x=1表示左邊界擴張, y同理 
struct node{
	int l,r,x,y;
}S[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;
	node &s=S[i][j][l][r][x][y];
	f[i][j][l][r][x][y]=dat,s.l=L,s.r=R,s.x=X,s.y=Y;
}
void print(int i,int j,int l,int r,int x,int y)
{
	if(!j)return;node &s=S[i][j][l][r][x][y];
	print(i-1,j-(r-l+1),s.l,s.r,s.x,s.y);
	_rep(j,l,r)printf("%d %d\n",i,j);
}
int main()
{
	//freopen("in.txt","r",stdin);
	scanf("%d%d%d",&n,&m,&K);memset(f,0xcf,sizeof(f));
    _rep(i,1,n)_rep(j,1,m)scanf("%d",&a[i][j]),sum[i][j]=sum[i][j-1]+a[i][j];
    _rep(i,1,n)_rep(j,1,K)_rep(l,1,m)_rep(r,l,m)
    {
    	k=r-l+1;if(k>j)break;
    	now=sum[i][r]-sum[i][l-1];
		_for(x,0,2)_for(y,0,2)update(now,m,0,x,y);
    	x=y=1;
    	//左右邊界都在擴張 
    	_rep(p,l,r)_rep(q,p,r)update(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
    	x=y=0;
    	//左右邊界都在收縮 
    	_rep(p,1,l)_rep(q,r,m)
    	{
    		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;
		//左擴右縮 
		_rep(p,l,r)_rep(q,r,m)
		{
			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;
		//左縮右擴 
		_rep(p,1,l)_rep(q,l,r)
		{
			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);
		}
	}
	_rep(i,1,n)_rep(l,1,m)_rep(r,1,m)_for(x,0,2)_for(y,0,2)if(ans<f[i][K][l][r][x][y])ans=f[i][K][l][r][x][y],si=i,sl=l,sr=r,sx=x,sy=y;
	printf("Oil : %d\n",ans);
	print(si,K,sl,sr,sx,sy);
	return 0;
}

總結

高難度的線性DP,首先需要想到每一個狀態需要哪些維度來描述,在行數和個數兩個維度上符合“階段線性增長”的特點。還要就是DP中記錄方案的方法。