P2258 子矩阵

斯德哥尔摩

2018-10-20 23:07:52

Personal

[P2258 子矩阵](https://www.luogu.org/problemnew/show/P2258) 本蒟蒻表示一眼看错了题面。。。 把子矩阵理解错了,认为是一道沙茶题。。。 然后发现,这个样例好像不对啊。。。 回头看题,我就是个$ZZ$——子矩阵:选取某些行和某些列交叉位置所组成的新矩阵。。。 坑爹无极限。。。 考虑暴力搜索,从数据规模来看可以知道时间复杂度铁定$TLE$。。。 然后从暴力的基础上优化。 我们发现我们主要是选取行和列的时候耗费了大量时间。 那么我们就考虑使用枚举行的选择情况,即类似全排列的组合情况。 确定行的选取情况后,用$dp$来求出最小的列情况。 由于$dp$和搜索本来就是差不多的东西,所以正确性可以保证。 最后扫一遍所有的$dp$值,输出最小即可。 然后这个题的关键就变成了:如何正确地$dp$。 设$dp[i][j]$代表选择了$i$列且最后一列选择原图中第$j$列的差的绝对值和的最小值。 每次利用前缀和算出每列之间的分数$w$,两列之间的分数$sum$。 转移方程长这个样: $$dp[i][j]=\min\{dp[i-1][k]+w[j]+v[j][k]\}$$ 复杂的上限是$O(C_{16}^8\times 16^3)$,可以过。 附代码: ```cpp #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cmath> #define MAXN 20 using namespace std; int n,m,r,c,ans=2147483646; int w[MAXN],line[MAXN]; int val[MAXN][MAXN],sum[MAXN][MAXN],dp[MAXN][MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } void solve(){ memset(w,0,sizeof(w)); memset(sum,0,sizeof(sum)); memset(dp,127,sizeof(dp)); for(int i=1;i<=m;i++){ for(int j=1;j<r;j++) w[i]+=abs(val[line[j]][i]-val[line[j+1]][i]); for(int j=i+1;j<=m;j++) for(int k=1;k<=r;k++) sum[i][j]+=abs(val[line[k]][i]-val[line[k]][j]); } dp[0][0]=0; for(int i=1;i<=c;i++) for(int j=i;j<=m;j++) for(int k=0;k<j;k++) dp[i][j]=min(dp[i][j],dp[i-1][k]+w[j]+sum[k][j]); for(int i=c;i<=m;i++)ans=min(ans,dp[c][i]); } void dfs(int x,int last){ if(x>r){ solve(); return; } for(int i=last+1;n-i>=r-x;i++){ line[x]=i; dfs(x+1,i); } } void work(){ dfs(1,0); printf("%d\n",ans); } void init(){ n=read();m=read();r=read();c=read(); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) val[i][j]=read(); } int main(){ init(); work(); return 0; } ```