P2258 [NOIP2014 普及组] 子矩阵

 

做法的结构大致是这样的:首先枚举其行的排列情况(类似全排列,只是元素个数确定)。然后,对于每一种行的排列情况,DP出它列的最优情况(DP等会儿再讲),然后取所有行的排列情况的最优情况的最小值。这样,就只需要一维的枚举了,比暴力快了不少。有的人可能还会问,这样会不会也超时?我们来算一下。这样的结构时间复杂度是:O(C(n,n/2)*n^3),其中(C(n,n/2)是一维枚举的时间复杂度,n^3是DP的时间复杂度。那么,对于所有数据,它最多的运算次数是:C(16,8)*16^3,大约是五千万,是可以通过的。所以只要把细节打出来,此题就过了。

那么,具体的算法如何实现呢?首先,枚举就不用讲了(代码里会讲到一点优化剪枝),然后就是DP的事了。问题就在于如何DP呢?其实,我们可以针对每一种枚举出来的行的情况都进行一次类似的DP。而针对于每一种情况,我们可以把它抽象化成一个更简单的问题:一个r*m矩阵,选择c列,问其相邻元素的差的绝对值的和最小是多少?在这个r*m的矩阵中,那r行其实就是我们已经枚举出来的几行,而m和c与原题中的m和c相同,从而将二维选择的问题降至了一维。

而DP也变得十分明显了:设f[i][j]表示在这个r*m的矩阵中,在其前i列中选择j列(且选的列中包括第i列),组成的子矩阵中,最小值(即其相邻元素的差的绝对值的和的最小值(之后的值等表达也是指的这个东西,即题目要求求出的值))是多少。这样,便可以很显然的推出动态转移方程:

##f[i][j]=min(f[k][j-1]+lc[i]+hc[i][k])

这里的lc[i]指的是第i列中所有元素的值,而hc[i][j]指的是仅对于第i列与第j列之间,所有同行元素的差的绝对值的和。懂线性DP的同学应该都能理解这个动态转移方程。而求lc与hc也很简单,直接在DP前暴力预处理即可。于是,DP便写完了。

而此题并没有到此结束,因为边界条件也是需要格外注意的。首先是k,k的取值范围是从j-1到i-1。i-1很好理解,而j-1是因为若k小于j-1,则k<j-1,f[k][j-1]这个状态也就自然不存在了(从定义上很好理解)。所以得出了k的取值范围。

另外需要注意的是边界情况,有两个边界:

###第一个是j==1的情况,此时f[i][j]=lc[i]

###第二个是j==i的情况,即前i列都取,所以f[i][j]=f[i-1][j-1]+lc[i]+hc[i][j-1]。将DP写好,此题便处理完成了。

 

》》》clear 时 要按实际意义memset

》》》f[i][j] j必须选 增加限制

》》》DP一定比搜索快

/*
5 6 2 2
5 4 2 6 6 2
4 10 1 8 2 4
1 9 3 10 10 5
6 4 8 3 1 4
6 8 4 10 2 10

4
*/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<bits/stdc++.h>
#define ll long long
#define ddd printf("-----------------debug\n");
using namespace std;

int r,c,v[2000][2000],x[2000],y[2000],n,m,ans=0x3f3f3f3f;
int lc[2000],hc[2000][2000],f[2000][2000];
void pre()
{
    memset(lc,0,sizeof(lc));
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++)
            hc[i][j]=0,f[i][j]=0x3f3f3f3f;
            
    for(int i=1;i<=m;i++)//预处理lc[i]
    {
       // lc[i]=0;
        for(int j=1;j<r;j++)
        {
            lc[i]+=abs(v[x[j]][i]-v[x[j+1]][i]);//计和
        }
    }
    for(int i=2;i<=m;i++)//预处理hc[i][j](前提条件:i>j)
    {
        for(int j=1;j<i;j++)
        {
            //hc[i][j]=0;
            for(int k=1;k<=r;k++)
            {
                hc[i][j]+=abs(v[x[k]][i]-v[x[k]][j]);//计和
            }
        }
    }

}
void cal()
{
    int cmin;
    for(int i=1;i<=m;i++)//枚举i
    {
       // cmin=min(i,c);//j的边界值(一定要注意不能大于c)
        for(int j=1;j<=min(c,i);j++)
        {
            if(j==1)//第一种边界
            {
                f[i][j]=lc[i];
            }
            else
            if(i==j)//第二种边界
            {
                f[i][j]=f[i-1][j-1]+lc[i]+hc[i][j-1];
            }
            else//正常情况
            {
                //f[i][j]=2e8;//初始化,取inf
                for(int k=j-1;k<i;k++)//注意边界
                {
                    f[i][j]=min(f[i][j],f[k][j-1]+lc[i]+hc[i][k]);//取最小值
                }
            }
        }
    }
    for(int i=1;i<=m;i++)
    ans=min(ans,f[i][c]);
}
void dfsx(int dep)
{
    
    if(dep==r+1)  {pre(); cal(); return;}
    for(int i=x[dep-1]+1;i<=n-r+dep;i++)
    {
        x[dep]=i;
        //if(dep<=r) 
            dfsx(dep+1);
        x[dep]=0;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    memset(x,0,sizeof(x)); memset(y,0,sizeof(y));
    
    cin>>n>>m>>r>>c;
    for(int i=1;i<=n;i++)//!!!!!!!!!!!!!!!!
        for(int j=1;j<=m;j++)
            cin>>v[i][j];
    dfsx(1);     
    cout<<ans<<endl;
    return 0;
}
View Code

 

 
posted @ 2023-07-24 08:12  JMXZ  阅读(121)  评论(0)    收藏  举报