[NOIP2014][vijos1914]子矩阵(dp)

来源:互联网 发布:php超全局变量 编辑:程序博客网 时间:2024/05/29 06:57

题目描述

传送门

题解

数据范围这么小,直接上状压dp了。。。然而写完之后发现大家都写得是暴搜+dp,而且TA竟然还用暴搜直接艹掉了!!!
其实dp的思路都是差不多的。如果用暴搜的话,就是搜出来选哪些行,然后令f(i,j)表示选到第i列已经选了j列的最小分数,diff(i,j)表示第i列和第j列差的分数。然后f(i,j)=min{f(k,j-1)+diff(k,i)},1<=k<i.
可是我把暴搜的过程直接状压了。可以发现每一行的状态至多只有C816种,那么令f(i,j,k)表示前i行选了j行(第i行必选)状态编号为k的最优解,可以预处理出来dif(i,j)表示第i行在第j个状态时自己本身产生的答案,diff(i,j,k)表示第i行和第j行在第k个状态时之间产生的答案,那么f(i,j,k)=min{f(l,j-1,k)+dif(i)+diff(i,l,k)},1<=l<i.也非常好转移。
做完之后膜拜了ta关于暴搜+剪枝复杂度的证明!!以后这种证明复杂度的问题还是要自己多想想。

代码

#include<iostream>#include<cstring>#include<cstdio>using namespace std;int n,m,r,c,inf,ans;int a[20][20],sol[15000],f[20][20][15000],dif[20][15000],diff[20][20][15000];int Abs(int x){    return (x>0)?x:-x;}int calc(int x){    int ans=0;    while (x)    {        ans+=(x&1);        x>>=1;    }    return ans;}int _calc(int id,int x){    int ans=0,dig[20];dig[0]=0;    for (int i=0;i<m;++i)        if ((x>>i)&1) dig[++dig[0]]=m-i;    for (int i=dig[0];i>1;--i)        ans+=Abs(a[id][dig[i]]-a[id][dig[i-1]]);    return ans;}int __calc(int id,int jd,int x){    int ans=0;    for (int i=0;i<m;++i)        if ((x>>i)&1) ans+=Abs(a[id][m-i]-a[jd][m-i]);    return ans;}int main(){    scanf("%d%d%d%d",&n,&m,&r,&c);    for (int i=1;i<=n;++i)        for (int j=1;j<=m;++j) scanf("%d",&a[i][j]);    for (int i=0;i<=(1<<m)-1;++i)        if (calc(i)==c) sol[++sol[0]]=i;    for (int i=1;i<=n;++i)        for (int j=1;j<=sol[0];++j)            dif[i][j]=_calc(i,sol[j]);    for (int i=1;i<=n;++i)        for (int j=i+1;j<=n;++j)            for (int k=1;k<=sol[0];++k)                diff[i][j][k]=__calc(i,j,sol[k]);    memset(f,127,sizeof(f));inf=f[0][0][0];    for (int i=1;i<=n;++i)        for (int j=1;j<=sol[0];++j)            f[i][0][j]=0,f[i][1][j]=dif[i][j];    for (int i=2;i<=n;++i)        for (int j=1;j<=min(i,r);++j)            for (int k=1;k<=sol[0];++k)                for (int l=1;l<i;++l)                    if (f[l][j-1][k]!=inf)                        f[i][j][k]=min(f[i][j][k],f[l][j-1][k]+diff[l][i][k]+dif[i][k]);    ans=inf;    for (int i=1;i<=n;++i)        for (int j=1;j<=sol[0];++j)            ans=min(ans,f[i][r][j]);    printf("%d\n",ans);}

总结

①数据范围很小的时候不要光考虑状压,暴搜和dp结合的做法也要有意识地想一想。

0 0
原创粉丝点击