https://codeforces.com/problemsets/acmsguru/problem/99999/167
题目
A国将I国分成了N*M的方格,联合国只允许A国占领k个方格(鼓励占别人的领土……),每块方格下面有一定的石油,A国想选择k个方格,满足
- 这k个方格中任意两个方格可以只通过向两种方向(上下左右选择两种,可以交替移动)移动到达,不能经过没有选的方格
- 这k个方格包含的石油数量最大
问该如何这些选择方格,$1\leqslant N,M\leqslant 15, 0\leqslant k\leqslant 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\times4)=\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\n", 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\n", ans); if(ans) print(ai, k, al, ar, adl, adr); //答案为0的时候没有方块 return 0; }