Noip2014普及组T4 子矩阵题解

Noip2014普及组 T4 P2258子矩阵
定义:

  1. 子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。

  2. 相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。

  3. 矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。

题意:求使矩阵分值最小的子矩阵,输出分值

subtask 1:50 points
\(n,m \in [1,12],\ a_{ij}\in [1,20]\)
暴力枚举出每个可能的子矩阵,直接按照题意算分值,在ans中取min

const int N=20;
const int M=1e5;
const int inf=0x3f3f3f3f;
int ans=inf;
int n,m,r,c,a[N][N],b[N][N];
int p1,p2,q1[M],q2[M];
int x2[N],y2[N];
int dirx[5]={0,-1,0,0,1},diry[5]={0,0,-1,1,0};

int score(){
    int sum=0;
    for(int i=1;i<=r;i++){
        for(int j=1;j<=c;j++){
            for(int k=1;k<=4;k++){
                int cx=i+dirx[k],cy=j+diry[k];
                if(cx>=1&&cx<=r&&cy>=1&&cy<=c)
                    sum+=abs(b[i][j]-b[cx][cy]);
            }
        }
    }
    return sum>>1;
}

int main(){
    n=read();m=read();
    r=read();c=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=read();
    
    for(int i=0;i<(1<<n);i++){
        int s=i,l=0,num=0;
        while(s>0){l=s&1;s>>=1;if(l)num++;}
        if(num==r) q1[++p1]=i;//利用二进制数枚举出满足次数的所有情况
    }
    
    for(int i=0;i<(1<<m);i++){
        int s=i,l=0,num=0;
        while(s>0){l=s&1;s>>=1;if(l)num++;}
        if(num==c) q2[++p2]=i;
    }
    
    for(int i=1;i<=p1;i++){
        for(int j=1;j<=p2;j++){//每次计算前的初始化
            int x=q1[i],y=q2[j],l1=n,l2=m,xp=0,yp=0,tmp=0;
            for(int i=1;i<=n;i++) x2[i]=0;
            for(int i=1;i<=m;i++) y2[i]=0;
            for(int w=1;w<=r;w++)
                for(int z=1;z<=c;z++)
                    b[w][z]=0;
            //将矩阵的哪些行与列被枚举到记录下来
            while(x>0){tmp=x&1;x>>=1;if(tmp)x2[++xp]=l1;l1--;}
            while(y>0){tmp=y&1;y>>=1;if(tmp)y2[++yp]=l2;l2--;}
            //创建一个新的矩阵用来计算分值
            for(int w=1;w<=r;w++){
                for(int z=1;z<=c;z++){
                    b[w][z]=a[x2[w]][y2[z]];
                }
            }
            ans=min(ans,score());
        }
    }
    cout << ans << endl;
    return 0;
}

subtask 2:100 points
利用dp来优化,只需枚举行的情况,列用dp算出最优值
\(dp[i][j]\)表示在前i列选j列(包括选第i列)的值,状态转移方程为\[dp[i][j]=min(dp[k][j-1]+lc[i]+hc[k][i])\]
其中,\(lc[i]\)表示当前列竖直连线的分值,\(hc[k][i]\)表示第i列与第k列之间水平连线的分值

#include <bits/stdc++.h>
using namespace std;

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

const int N=20;
const int M=1e5;
const int inf=0x3f3f3f3f;
int ans=inf;
int n,m,r,c,a[N][N],b[N][N];
int p1,q1[M],x1[N];
int dp[N][N],lc[N],hc[N][N];

void work(){
    memset(dp,0x3f,sizeof dp);
    for(int i=1;i<=m;i++)
        dp[i][1]=lc[i];
    for(int i=2;i<=m;i++)
        dp[i][i]=dp[i-1][i-1]+lc[i]+hc[i-1][i];
    for(int i=1;i<=m;i++){
        for(int j=2;j<i;j++){
            for(int k=j-1;k<=i-1;k++){
                dp[i][j]=min(dp[i][j],dp[k][j-1]+lc[i]+hc[k][i]);
            }
        }
    }
    for(int i=c;i<=m;i++)
        ans=min(ans,dp[i][c]);
}

void init(){//创建lc与hc两个关键数组
    memset(lc,0,sizeof lc);
    memset(hc,0,sizeof hc);
    for(int z=1;z<=m;z++)
        for(int w=1;w<r;w++)
            lc[z]+=abs(b[w][z]-b[w+1][z]);
        
    for(int z=1;z<=m;z++){
        for(int j=1;j<=m;j++){
            if(z==j) continue;
            for(int w=1;w<=r;w++)
                hc[z][j]+=abs(b[w][z]-b[w][j]);
        }
    }
}

int main(){
    //……读入 //枚举行的情况同上份代码
    for(int i=0;i<(1<<n);i++){
        int s=i,l=0,num=0;
        while(s>0){l=s&1;s>>=1;if(l)num++;}
        if(num==r) q1[++p1]=i;
    }
    
    for(int i=1;i<=p1;i++){
        int x=q1[i],l1=n,xp=0,tmp=0;
        while(x>0){tmp=x&1;x>>=1;if(tmp)x1[++xp]=l1;l1--;}
        //创建新矩阵
        for(int w=1;w<=xp;w++)
            for(int z=1;z<=m;z++)
                b[w][z]=a[x1[w]][z];
        init();
        work();
    }
    
    cout << ans << endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zhangyuhang253/p/10887286.html