HDU 1693(状态压缩 插头DP)
我们引用国家队2008年陈丹琦的大作——《基于连通性状态压缩的动态规划问题》,上面对于插头、轮廓线的概念有详细的解释,不再赘述。
我们使用一个三维数组,前两维表示所在的格子,后一维表示轮廓线的状况,值为方案数。
在每一行开始前,我们需要把上一行最右的轮廓线转换为这一行最左的轮廓线,因此执行一次左移操作(实际上轮廓线是进行了向右的一次滑动)
然后枚举所在单元格对每个轮廓线的影响 在论文中我们可以发现轮廓线总是包围一个格子的上部分和左部分 我们认为如果有通路与轮廓线重合则为1否则为0
因此很容易想象出来(当然也可以看论文)各个状态之间的转移关系 对于可通行的格子 分部分覆盖和全部覆盖以及全部不覆盖的情况讨论
对不可通行的格子必须将不可能的覆盖方案置为0 到最后的结果就是所求的方案了即dp[m][n][0]
感谢DK大牛的指导 本人5天来的苦思冥想终于得到了成果 1693 0ms 排行统计第一....
以下为本人代码:
#include <iostream>
using namespace std;
typedef __int64 LL;
LL dp[12][12][1<<12];
long hash[12][12];
int main()
{
long T;
long b=1;
scanf("%ld",&T);
while (T--)
{
long i,j,k,m,n;
scanf("%ld %ld",&m,&n);
for (i=1;i<=m;++i)
{
for (j=1;j<=n;++j)
{
scanf("%ld",&hash[i][j]);
}
}
dp[0][n][0]=1;
for (i=1;i<=m;++i)
{
long len=1<<n;
for (j=0;j<len;++j)
{
dp[i][0][j<<1]=dp[i-1][n][j];
}
for (j=1;j<=n;++j)
{
len=(1<<n<<1);
for(k=0;k<len;++k)
{
long p=1<<j;
long q=p>>1;
bool x=p&k;
bool y=q&k;
if (hash[i][j])
{
dp[i][j][k]=dp[i][j-1][k^p^q];
if (x!=y)
{
dp[i][j][k]+=dp[i][j-1][k];
}
}
else
{
if (x==0&&y==0)
{
dp[i][j][k]=dp[i][j-1][k];
}
else
{
dp[i][j][k]=0;
}
}
}
}
}
printf("Case %ld: There are %I64d ways to eat the trees.\n",b++,dp[m][n][0]);
}
return 0;
}
using namespace std;
typedef __int64 LL;
LL dp[12][12][1<<12];
long hash[12][12];
int main()
{
long T;
long b=1;
scanf("%ld",&T);
while (T--)
{
long i,j,k,m,n;
scanf("%ld %ld",&m,&n);
for (i=1;i<=m;++i)
{
for (j=1;j<=n;++j)
{
scanf("%ld",&hash[i][j]);
}
}
dp[0][n][0]=1;
for (i=1;i<=m;++i)
{
long len=1<<n;
for (j=0;j<len;++j)
{
dp[i][0][j<<1]=dp[i-1][n][j];
}
for (j=1;j<=n;++j)
{
len=(1<<n<<1);
for(k=0;k<len;++k)
{
long p=1<<j;
long q=p>>1;
bool x=p&k;
bool y=q&k;
if (hash[i][j])
{
dp[i][j][k]=dp[i][j-1][k^p^q];
if (x!=y)
{
dp[i][j][k]+=dp[i][j-1][k];
}
}
else
{
if (x==0&&y==0)
{
dp[i][j][k]=dp[i][j-1][k];
}
else
{
dp[i][j][k]=0;
}
}
}
}
}
printf("Case %ld: There are %I64d ways to eat the trees.\n",b++,dp[m][n][0]);
}
return 0;
}
以下为DK大人的sample代码,我加上了注释:
#include<iostream>
using namespace std;
int mat[100][100];
__int64 dp[12][12][1<<12];
int main()
{
// freopen("1.txt","r",stdin);
int zu;
int g=1;
scanf("%d",&zu);
while(zu--)
{
int m,n;
scanf("%d%d",&m,&n);
int i,j,k;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
scanf("%d",&mat[i][j]);
//memset(dp,0,sizeof(dp));
dp[0][n][0]=1;
for(i=1;i<=m;i++)
{
for(j=0;j<(1<<n);j++)
dp[i][0][j<<1]=dp[i-1][n][j];
//把最右边的去掉 把所有的状态拉到下一行并进行合理的改变(注意最左边的是低位所代表的数)
//上一行最右边的边格一定不与轮廓线重合 下一行最左边的边格也一定不与轮廓线重合
//所以进行的是上一行所有状态(也就是轮廓线)向右的一次滑动
for(j=1;j<=n;j++)//枚举决策线的拐向
{
for(k=0;k<(1<<n<<1);k++)//枚举轮廓线
{
int p=1<<j;//第j个轮廓段(上)
int q=p>>1;//第j-1个轮廓段(左)
bool x=k&p;//左轮廓是否与通路相交
bool y=k&q;//上轮廓是否与通路相交
//判断轮廓线在[i,j]为拐向时的分布 每个格子有1<<n<<1种 多阶段决策
if(mat[i][j])//如果该单元可以通行
{
dp[i][j][k]=dp[i][j-1][k^p^q];//必然有一个通路连接上一个格的通路
if(x!=y)
dp[i][j][k]+=dp[i][j-1][k];//有一处新覆盖则有另外的一种情况
}
else//否则为障碍格子
{
if(x==0&&y==0)//通路与轮廓线不相交
dp[i][j][k]=dp[i][j-1][k];//直接转移
else
dp[i][j][k]=0;//通路与轮廓线相交的方案一定为0
}
}
}
}
printf("Case %d: There are %I64d ways to eat the trees.\n",g++,dp[m][n][0]);
}
return 0;
}
using namespace std;
int mat[100][100];
__int64 dp[12][12][1<<12];
int main()
{
// freopen("1.txt","r",stdin);
int zu;
int g=1;
scanf("%d",&zu);
while(zu--)
{
int m,n;
scanf("%d%d",&m,&n);
int i,j,k;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
scanf("%d",&mat[i][j]);
//memset(dp,0,sizeof(dp));
dp[0][n][0]=1;
for(i=1;i<=m;i++)
{
for(j=0;j<(1<<n);j++)
dp[i][0][j<<1]=dp[i-1][n][j];
//把最右边的去掉 把所有的状态拉到下一行并进行合理的改变(注意最左边的是低位所代表的数)
//上一行最右边的边格一定不与轮廓线重合 下一行最左边的边格也一定不与轮廓线重合
//所以进行的是上一行所有状态(也就是轮廓线)向右的一次滑动
for(j=1;j<=n;j++)//枚举决策线的拐向
{
for(k=0;k<(1<<n<<1);k++)//枚举轮廓线
{
int p=1<<j;//第j个轮廓段(上)
int q=p>>1;//第j-1个轮廓段(左)
bool x=k&p;//左轮廓是否与通路相交
bool y=k&q;//上轮廓是否与通路相交
//判断轮廓线在[i,j]为拐向时的分布 每个格子有1<<n<<1种 多阶段决策
if(mat[i][j])//如果该单元可以通行
{
dp[i][j][k]=dp[i][j-1][k^p^q];//必然有一个通路连接上一个格的通路
if(x!=y)
dp[i][j][k]+=dp[i][j-1][k];//有一处新覆盖则有另外的一种情况
}
else//否则为障碍格子
{
if(x==0&&y==0)//通路与轮廓线不相交
dp[i][j][k]=dp[i][j-1][k];//直接转移
else
dp[i][j][k]=0;//通路与轮廓线相交的方案一定为0
}
}
}
}
printf("Case %d: There are %I64d ways to eat the trees.\n",g++,dp[m][n][0]);
}
return 0;
}