hdu 1693 Eat the Trees 轮廓线 插头dp

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=1693

题目意思:

给n*m的方格,有些方格有障碍,问有多少种方式能使所有的非障碍点都在某个环上,环可以有多个。

解题思路:

轮廓线dp.

dp[i][j][s]表示到达第i行第j列,轮廓线上插头的状态为s时的总的方案数。

对于当前第j列,状态(bool) s&(1<<j)表示当前格的右插头,(bool) s&(1<<(j-1)表示当前格的下插头。

容易知:

当前格为通行状态时:00<=11     01<=10,01      10<=10,01     11<=00 

当前格为障碍状态时:00<=00     其他为0

初始化为dp[0][m][0]=1 ;表示初始状态

上一行的最后一列的状态可以左移一位作为下一行的初始状态。

因为对于每一行的最后一列状态有效状态 K0K1K2..Km  Km一定为0,不可能有右插头

对于每一行的开始一列的前一状态 K0K1K2..Km K0一定为0,不可能存在第一格的左插头。

所以可以左移一位,作为下一行的初始状态。(注意下标从左置右为从小到大)

代码:

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#define eps 1e-6
#define INF 0x1f1f1f1f
#define PI acos(-1.0)
#define ll __int64
#define lson l,m,(rt<<1)
#define rson m+1,r,(rt<<1)|1
using namespace std;

/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/
ll dp[15][15][1<<15];
int save[15][15];

int main()
{
   int t,n,m;

   scanf("%d",&t);
   for(int ca=1;ca<=t;ca++)
   {
      scanf("%d%d",&n,&m);
      for(int i=1;i<=n;i++)
         for(int j=1;j<=m;j++)
            scanf("%d",&save[i][j]);
      dp[0][m][0]=1; //初始化状态
      for(int i=1;i<=n;i++)
      {
         for(int j=0;j<(1<<(m+1));j++)  //换行的时候,只需将状态左移一位
            dp[i][0][j<<1]=dp[i-1][m][j];//从k0k1k2...k(m-1)0 到0k1k2k3...km,只需左移一位
         for(int j=1;j<=m;j++)
         {
            for(int k=0;k<(1<<(m+1));k++)
            {
               int p=1<<j; //当前格子的右插头 //分别从上一格子的 相对于当前格子的左插头和上插头递推过来
               int q=p>>1; //当前格子的下插头
               bool x=k&p; //是否有插头,将多位转化为1位
               bool y=k&q; //是否有插头

               if(save[i][j]) //可通
               {
                  dp[i][j][k]=dp[i][j-1][k^p^q]; //相当于这两位取反 00<=11
                                                                  //01<=10 10<=01 11<=00
                                                   //对于每个状态都有一条路联通,另外01<=01 10<=10
                  if(x!=y) //新增加的情况 10<=10 01<=10
                     dp[i][j][k]+=dp[i][j-1][k];
               }
               else //有障碍物
               {
                  if(x==0&&y==0) //均为0
                     dp[i][j][k]=dp[i][j-1][k]; //前一格子也为0,0,前面一块可以连通
                  else
                     dp[i][j][k]=0;  //不为0,对于这种状态只能标志为0
               }
            }
         }
      }
      printf("Case %d: There are %I64d ways to eat the trees.\n",ca,dp[n][m][0]);
     // printf("%d\n",dp[n][m][0]); //轮廓线为0(没有插头)的情况

   }

   return 0;
}





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值