bzoj[SCOI2005]最大子矩阵

13 篇文章 0 订阅

描述

这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的k个子矩阵不能相互重叠。

格式

输入格式

第一行为n,m,k(1≤n≤100,1≤m≤2,1≤k≤10),接下来n行描述矩阵每行中的每个元素的分值(每个元素的分值的绝对值不超过32767)。

输出格式

只有一行为k个子矩阵分值之和最大为多少。

样例

样例输入
3 2 2
1 -3
2 3
-2 3

样例输出
9

限制

每个测试点1s

来源

NOI2005四川省选拔赛第二试第4题

当时的省选题,放在现在难度已经不大了。如果这道题的 m 也是 1-100 ,那么就不好办了,好在它最大只有 2 = =。这个时候我们就可以分治处理,m=1 的时候怎么处理,m=2 的时候怎么处理,放在考场上这是个很实用的技巧,在想不出正解的时候分治处理特殊测试数据。

m=1 的情况应该很好想到,用 f[i][k] 表示这一列取到第 i 个数正好用了 k 个矩阵的最优解,就由几个状态转移过来:
1、不选第 i 个数。
2、选这个数构成一个新的矩阵,也就是从 k-1 到 i-1 中不取某个数。
方程:
for(int j=i-1;j>=k-1;j–)
f[i][k]=max(f[i][k],f[j][k-1]+sum[i][1]-sum[j][1]);
为什么是到 k-1 呢?这是因为状态是有选k-1个推过来,选的数肯定是大于等于构成的矩阵的数的。

m=1 的情况搞清楚了,那么m=2 的情况也就迎刃而解了。我们用f[i][j][k]表示第 1 列取了 i 个,第 2 列取了 j 个,正好用了 k 个矩阵,那么这个状态就要推两次上面的方程,分别是第一列的和第二列的。特殊情况,如果 i==j ,那么再推一次两列一起的就可以了。
代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int map[110][20],f[1100][110][20];
int sum[110][20];
int main()
{
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>map[i][j];
            sum[i][j]=sum[i-1][j]+map[i][j];
        }
    if(m==1)
    {
        f[1][1][1]=map[1][1];
        for(int i=1;i<=n;i++)
            for(int j=1;j<=k;j++)
            {
                f[i][1][j]=f[i-1][1][j];
                for(int kk=i-1;kk>=j-1;kk--)
                    f[i][1][j]=max(f[i][1][j],f[kk][1][j-1]+sum[i][1]-sum[kk][1]);
            }
        cout<<f[n][1][k];
        return 0;
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int kk=1;kk<=k;kk++)
            {
                f[i][j][kk]=max(f[i-1][j][kk],f[i][j-1][kk]);
                for(int t=i-1;t>=kk-1;t--)
                    f[i][j][kk]=max(f[i][j][kk],f[t][j][kk-1]+sum[i][1]-sum[t][1]);
                for(int t=j-1;t>=kk-1;t--)
                    f[i][j][kk]=max(f[i][j][kk],f[i][t][kk-1]+sum[j][2]-sum[t][2]);
                if(i==j)
                    for(int t=i-1;t>=kk-1;t--)
                        f[i][j][kk]=max(f[i][j][kk],f[t][t][kk-1]+sum[i][1]-sum[t][1]+sum[j][2]-sum[t][2]);
            }
    cout<<f[n][n][k];
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值