这道题已经做了很久了。

luogu 2258 子矩阵

S1

说实话,这道题已经做了很久了。最开始的思路是想要直接DP,想过四维的但是被证伪了,状压的话一定会MLE,就暂时搁置了。

S2

今天重新捡起来这道题,第一个想到的算法就是DFS。但是如果搜行然后搜列的话就特别暴力,完全没有优化的思路。

S3

看题解
其实仔细思考的话应该能想出来。
正确的方法应该是先用DFS搜行的全组合,之后再用DP,f[i][j]表示选i列,最后一列是j的时候的最小值。因为数据实在是小,这个完全可以实现。
想到了状态转移方程f[i][j]=min(f[i][j],f[i-1][k]+l[j]+s[k][j]);
那么DP反而不是难点,难点在于DFS,这个时间复杂度有点看脸,试着写写看。
一遍过了,但是过程并不轻松,犯了两个错误。
1.l[i]没有在每一个组合都更新。l[i]是选好的行上面第i列的分值之和,而不是总的 。
2.在状态转移的时候,对于每一个f[i][j]都用了f[i-1][0]来推,因为f[i][0]=l[i],但除了第1列之外并不能用第0列来推,以后状态转移的时候一定要注意这一点,一定要单独考虑考虑第0行或者第0列。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define rint register int
#define inv inline void
#define big 1e9
using namespace std;
int s[21][21],f[21][21],l[21],q[21],a[21][21],n,m,ans,r,c;
inv dp()
{
    for (rint i=1;i<=m;i++)
    {
        l[i]=0; 
        for (rint j=2;j<=r;j++)
            l[i]+=abs(a[q[j]][i]-a[q[j-1]][i]);
    }
    for (rint i=1;i<=m-1;i++)
        for (rint j=i+1;j<=m;j++)
        {
            s[i][j]=0;
            for (rint k=1;k<=r;k++)
                s[i][j]+=abs(a[q[k]][i]-a[q[k]][j]);
        }
    for (rint i=1;i<=c;i++)
        for (rint j=1;j<=m;j++)
            f[i][j]=big;
    for (rint i=1;i<=c;i++)
        for (rint j=i;j<=m;j++)
            for (rint k=i-1;k<=j-1;k++)
                f[i][j]=min(f[i][j],f[i-1][k]+l[j]+s[k][j]);
    for (rint i=1;i<=m;i++)
        ans=min(ans,f[c][i]); 
}
inv dfs(int x,int dep)
{	
    q[dep]=x;
    if (dep==r)
    {
        dp();
        return; 
    }
    for (rint i=x+1;i<=n-r+dep+1;i++) dfs(i,dep+1);
    q[dep]=0;
}
int main()
{
    scanf("%d%d%d%d",&n,&m,&r,&c);
    for (rint i=1;i<=n;i++)
        for (rint j=1;j<=m;j++)
            scanf("%d",&a[i][j]);
    ans=big;
    for (rint i=1;i<=n-r+1;i++)
        dfs(i,1);
    printf("%d",ans);
} 

总结:

  1. 一个算法想了一很久想不出来,有可能是自己的能力到了极限,也有可能是根本就不用这个算法,试着开辟一下新路。
  2. 在没有办法的时候,大胆的尝试DFS,只要剪枝剪得好不是没有可能AC
  3. DFS和DP都不是很精通,一定要多做题把题感练回来。
  4. 以后状态转移的时候一定要注意单独考虑考虑第0行或者第0列。
赞 赏
蒟蒻的邮箱:xmzl200201@126.com