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; }
                    
                
                
            
        
浙公网安备 33010602011771号