【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; }