【Luogu】P2258子矩阵(状态压缩,DP)

233今天蒟蒻我连文化课都没听光想着这个

然后我调了一下午终于过了!!!

一看数据范围似乎是状压,然而216等于65536。开一个65536*65536的二维数组似乎不太现实。

所以Rqy在四月还是几月给我们讲这道题的时候说要半DFS半DP,时间复杂度O(2n*n3)

怎么个半DFS半DP法呢?

其实我没DFS。所以这个问题不重要。

我真的没用DFS。枚举从1到2n-1的所有集合,把二进制数中1的个数不等于r的都筛掉。然后对于每个状态,预处理出每一列内部的代价,预处理出列与列之间的代价,然后进行DP。

那DP状态怎么设计呢?

我们设f[i][j]表示考虑前i列,选择j列,且必须选择第i列的最小代价。则转移方程为f[i][j]=min(f[i][j],f[k][j-1]+第i列内部代价+第k列和第i列之间的代价) 1<=k<j

所以必须选择第i列的道理就是转移方程好设计。如果没有这个限制,可能你在算第k列和第i列之间的代价的时候第k列根本没被选上,结果就WA。

最后ans=min(ans,f[i][c])  1<=i<=m

代码如下

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#define Max ((1<<n)-1)
inline long long read(){
    long long num=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')    f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        num=num*10+ch-'0';
        ch=getchar();
    }
    return num*f;
}
inline long long min(long long a,long long b){    return a<b?a:b;    }
int que[1000][1000];
int f[20][20];
int d[20],w[20][20];
bool s[100];
int ans=0x7fffffff;
inline int count(int x){
    int ans=0;
    while(x){
        x&=(x-1);
        ans++;
    }
    return ans;
}

int main(){
    int n=read(),m=read(),r=read(),c=read();
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)    que[i][j]=read();
    for(int i=1;i<=Max;++i){
        if(count(i)!=r)    continue;
        memset(d,0,sizeof(d));
        memset(f,127/3,sizeof(f));
        memset(w,0,sizeof(w));
        int x=i;
        for(int j=1;j<=n;++j){
            s[j]=x&1;
            x>>=1;
        }
        for(int j=1;j<=m;++j)
            for(int k=1;k<=n;++k)
                if(s[k]){
                    for(int u=1;u<j;++u)
                        w[j][u]+=std::abs(que[k][j]-que[k][u]);
                    int l=k-1;
                    while(!s[l]&&l)    l--;
                    if(!l)    continue;
                    d[j]+=std::abs(que[k][j]-que[l][j]);
                }
        for(int j=1;j<=m;++j){
            f[j][0]=0;
            f[j][1]=d[j];
        }
        for(int j=1;j<=m;++j)
            for(int k=1;k<=j&&k<=c;++k){
                for(int l=1;l<j;++l){
                    if(f[j][k-1]==f[0][0])    continue;
                    f[j][k]=min(f[j][k],f[l][k-1]+w[j][l]+d[j]);
                }
            }
        for(int j=c;j<=m;++j)    ans=min(ans,f[j][c]);
    }
    printf("%d",ans);
    return 0;
}

 

posted @ 2017-09-07 20:05  Konoset  阅读(691)  评论(0编辑  收藏  举报