题意:一个棋盘,横竖线都是从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;
}