https://codeforces.com/problemsets/acmsguru/problem/99999/167
题目
A国将I国分成了N*M的方格,联合国只允许A国占领k个方格(鼓励占别人的领土……),每块方格下面有一定的石油,A国想选择k个方格,满足
- 这k个方格中任意两个方格可以只通过向两种方向(上下左右选择两种,可以交替移动)移动到达,不能经过没有选的方格
- 这k个方格包含的石油数量最大
问该如何这些选择方格,$1leqslant N,Mleqslant 15, 0leqslant kleqslant NM$
时间 750ms,内存65536kb
题解
方格肯定是连通的,然后还有只能向两种方向移动到达,说明这个连通块不能出现凹进去的情况,也就是必须是凸的
那么,把连通块分成几行,从上往下每一行的左边界先递减,后递增,从上往下每一行的右边界先递增,后递减
令$dp[i][cnt][l][r][dl][dr]$为前i行选择了cnt个方块,第i行选择了l~r的方块,左边界变化的情况是dl,右边界变化的情况是dr。
那么,就有很多转移
- 开始
- 变化情况不变
- 左边界从递减变成递增
- 右边界从递增变成递减
- 左右边界一起变成><
注意转移的时候依然要保证凸连通性
写了一半发现不能中途结束……于是在转移的时候就更新答案了……但是又忘了开始和k=0的情况
转移需要枚举原来的l和原来的r,同时还需要枚举dl和dr,时间复杂度$mathcal{O}(NKMM imes4)=mathcal{O}(4N^5)$,有点悬,但是没有优化常数就过了
由于内存限制得很紧,保存转移的数据都需要改用unsigned char来存储,顺便把dp的第一维减小到2
AC代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
using namespace std;
typedef long long ll;
#define MAXN 17
typedef unsigned char uchar;
int dp[2][MAXN*MAXN][MAXN][MAXN][2][2];
uchar prel[MAXN][MAXN*MAXN][MAXN][MAXN][2][2];
uchar prer[MAXN][MAXN*MAXN][MAXN][MAXN][2][2];
uchar predl[MAXN][MAXN*MAXN][MAXN][MAXN][2][2];
uchar predr[MAXN][MAXN*MAXN][MAXN][MAXN][2][2];
int a[MAXN][MAXN], b[MAXN][MAXN];
int n,m,k;
int ans;
uchar ai, al, ar, adl, adr;
inline void update(uchar i, uchar pl, uchar pr, uchar pdl, uchar pdr,
uchar nnum, uchar nl, uchar nr, uchar ndl, uchar ndr) {
if(pl>pr) return;
if(pl<0 || pr>=m) return;
int pnum = nnum-(nr-nl+1); if(pnum<pr-pl+1) return;
int val = dp[(i&1)^1][pnum][pl][pr][pdl][pdr];
val += b[i][nr]-b[i][nl]+a[i][nl];
if(val>dp[i&1][nnum][nl][nr][ndl][ndr]) {
dp[i&1][nnum][nl][nr][ndl][ndr]=val;
prel[i][nnum][nl][nr][ndl][ndr]=pl;
prer[i][nnum][nl][nr][ndl][ndr]=pr;
predl[i][nnum][nl][nr][ndl][ndr]=pdl;
predr[i][nnum][nl][nr][ndl][ndr]=pdr;
if(val>ans) { //更新答案
ans=val, ai=i, al=nl, ar=nr, adl=ndl, adr=ndr;
}
}
}
void print(int i, int num, int l, int r, int dl, int dr) {
if(l==255) return; //开始
if(i) print(i-1, num-(r-l+1),
prel[i][num][l][r][dl][dr],
prer[i][num][l][r][dl][dr],
predl[i][num][l][r][dl][dr],
predr[i][num][l][r][dl][dr]);
REPE(z,l,r) {
printf("%d %d
", i+1, z+1);
}
}
const int dx[]={-1,1};
int main() {
scanf("%d%d%d", &n, &m, &k);
REP(i,0,n) REP(j,0,m) scanf("%d", &a[i][j]);
REP(i,0,n) b[i][0]=a[i][0];
REP(i,0,n) REP(j,1,m) b[i][j]=a[i][j]+b[i][j-1];
memset(dp[1],0,sizeof(dp[1]));
ans=0;
REP(i,0,n) {
memset(dp[i&1],0,sizeof(dp[i&1]));
REP(nl,0,m) for(int nr=nl; nr<m && nr-nl+1<=k; nr++) {
int val=dp[i&1][nr-nl+1][nl][nr][0][1]=b[i][nr]-b[i][nl]+a[i][nl]; //开始
prel[i][nr-nl+1][nl][nr][0][1]=255;
if(val>ans) { //更新答案
ans=val, ai=i, al=nl, ar=nr, adl=0, adr=1;
}
REPE(num,nr-nl+1,k) REP(dl,0,2) REP(dr,0,2) { //保持状态不变
for(int pl=nl; pl>=0 && pl<m && pl<=nr; pl-=dx[dl])
for(int pr=nr; pr>=0 && pr<m && pr>=pl && pr>=nl; pr-=dx[dr]) {
update(i, pl, pr, dl, dr, num, nl, nr, dl, dr);
}
}
REPE(num,nr-nl+1,k) {
PERE(pl,nl,0) PERE(pr,nr,nl) //到 >>
update(i, pl, pr, 0, 1, num, nl, nr, 1, 1);
REP(pr,nr,m) REPE(pl,nl,nr) //到 <<
update(i, pl, pr, 0, 1, num, nl, nr, 0, 0);
PERE(pl,nl,0) REP(pr,nr,m) { //到 ><
update(i, pl, pr, 0, 1, num, nl, nr, 1, 0);
update(i, pl, pr, 0, 0, num, nl, nr, 1, 0);
update(i, pl, pr, 1, 1, num, nl, nr, 1, 0);
}
}
}
}
// int K=(n-1)&1;
printf("Oil : %d
", ans);
if(ans) print(ai, k, al, ar, adl, adr); //答案为0的时候没有方块
return 0;
}