题意:一个棋盘,横竖线都是从1到100标号(竖线从左到右标,横线从下到上标),输入n表示有n个被标记的格子,是给出这个格子的左下角坐标,然后输入m,在输入m个数,表示在这些竖线的地方切开棋盘(其实只切了m-2刀,因为2刀必须是1和100,相当于没有),然后输入A,表示你要在横上上切A刀(其实也只是A-2刀,因为2刀必须在横线的1和100)。那么就可以把棋盘很多个大小不一的方块(矩形),只要这些方块中有被标记的小格子(1个或多个),那么这个方块就是一个选区,我们是要使到选区的个数最多
思路: 有想着肯定是要枚举(i,j)行之间的最大值的,但是还是写不出来,看了题解,使用预处理[i,j]间的个数,然后就可以枚举啦 ,f[i][j]表示在前j行切i刀的最大值,并记录切的行数,因为第1,100 行是一定要切的,所以从第2刀开始枚举,j行从i+1开始就可以了,还有就是这道题具有最优子结构,就是第前j行切i刀的最大位置,一定是包含前p[i][j]行切i-1刀的结果,所以我们就得到打印结果的方式了
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAXN = 110; int N,S,A,g[MAXN][MAXN],f[MAXN][MAXN],street[MAXN]; int t[MAXN][MAXN],st[MAXN][MAXN][MAXN],s[MAXN][MAXN],p[MAXN][MAXN]; int init(){ int i,j,k,flag,x,y; scanf("%d",&N); if (N == -1) return 0; memset(g,0,sizeof(g)); for (i = 0; i < N; i++){ scanf("%d%d",&y,&x); g[x][y] = 1; } scanf("%d",&S); for (i = 0; i < S; i++) scanf("%d",&street[i]); scanf("%d",&A); memset(t,0,sizeof(t)); for (i = 2; i <= 100; i++) for (j = 1; j < S; j++){ flag = 0; for (k = street[j-1]; k < street[j]; k++) if (g[i-1][k]) //是左下角标记 flag = 1; if (flag) t[i][j] = 1; //前i行前j刀是否有标记的 } memset(s,0,sizeof(s)); memset(st,0,sizeof(st)); for (i = 1; i < 100; i ++) for (j = i + 1; j <= 100; j++){ for (k = 1; k < S; k++) st[i][j][k] |= st[i][j-1][k]; //确定在竖切的(i,j)区域的块数 for (k = 1; k < S; k++) // 确定j行加进去后有没有 st[i][j][k] |= t[j][k]; for (k = 1; k < S; k++) //确定有几个 if (st[i][j][k]) s[i][j]++; } return 1; } void print(int i,int k){ if (k != 1) print(i-1,p[i][k]); printf(" %d",k); } void solve(){ int i,j,k,max; memset(f,-1,sizeof(f)); for (i = 2; i <= 100; i++){ f[1][i] = s[1][i]; p[1][i] = 1; } for (i = 2; i < A; i++) for (j = i + 1; j <= 100; j++){ for (k = i; k < j; k++) if (f[i-1][k]+s[k][j] > f[i][j]){ f[i][j] = f[i-1][k] + s[k][j]; p[i][j] = k; } } printf("%d",A); print(A-1,100); printf("\n"); } int main(){ while (init()) solve(); return 0; }