当前位置: 代码迷 >> 综合 >> [caioj]1491: 基于连通性状态压缩的 动态规划问题:Tony's Tour
  详细解决方案

[caioj]1491: 基于连通性状态压缩的 动态规划问题:Tony's Tour

热度:85   发布时间:2023-10-29 12:57:52.0

【题目描述】
给你一个 n*m 的地图,有的格子存在障碍,求 从左下角走到右下角 且经过所有非障碍格子一次(仅一次)的路径总数。
【输入】
多组数据,每组数据开头两个整数 n,m(0< n,m<=8),接下来描述一个 n*m 的地图,“#”表示该格子有障碍,“.”表示该格子无障碍。当 n = 0 ,m = 0时,输入结束。
【输出】
对于每组数据,输出符合条件的路径条数。
【样例输入】
2 2
..
..
2 3

..


3 4
….
….
….
0 0
【样例输出】
1
1
4
来源
Poj 1739

我的做法

一开始我想只要到了(n,m),只要这个点只有一个插头,就累加。。
那有一个难点,就是怎么保证开始点在左下角
那么我们来想啊
左下角的话,当扩展到他的时候,他要么有一个上插头,要么没有
当他有上插头的时候,下一次就少一个左插头
否则就给一个左插头
【说实话,这个讨论还写了挺久】
写成代码大概长这样

if (u==n&&i==1)//是起点 {if (p==0&&q==0){change(st,i+1,1);ADD;}else {change(st,i+1,0);ADD;}continue;
}

然后终点判断也比较简单

if (u==n&&i==m)//终点
{if (p>0&&q>0) continue;//不可以有两个插头if (p==0&&q==0) continue;//不可以没有插头 ans=ans+sum;continue;
}

然后就可以了。。

#include<cstdio>
#include<cstring>
#define ADD add(st,sum)
typedef long long LL;
const int MOD=100037;
const int N=15;
bool map[N][N];//是不是可以走 
int n,m;
int xx=-1,yy;//我们要寻找一个最下 最右的点——>当到他的时候就可以累计答案了
LL ans=0;//答案个数
LL a[2][MOD];//这种状态的数目,其实差不多是来辅助下面的数组 
int b[2][MOD];//状态
int num[2];//状态个数
int HASH[MOD];
/*0:无插头状态 1:左括号插头 2:右括号插头*/
int get (int x,int p)//第j位 当然是四进制里面的第j位啦 
{
   return (x>>(p-1)*2)&3;}
int now;
void add (int st,LL sum)//给当前的决策状态加上这个状态
{int ss=st%MOD;while (HASH[ss]!=-1&&b[now][HASH[ss]]!=st){ss++;ss%=MOD;if (ss==0) ss=1;}if (HASH[ss]==-1){HASH[ss]=++num[now];b[now][num[now]]=st;a[now][num[now]]=sum;}else a[now][HASH[ss]]+=sum;
}
void change (int &s,int p,int v)//把s的第p位改为v
{s=s^(get(s,p)<<(p-1)*2);s=s^(v<<(p-1)*2);
}
void solve ()
{a[0][1]=1;num[0]=1;b[0][1]=0;for (int u=1;u<=n;u++){for (int i=1;i<=m;i++)//当前的转移节点,我们要尝试吧这个节点转到下一个{now^=1;num[now]=0;memset(HASH,-1,sizeof(HASH));for (int k=1;k<=num[now^1];k++)//继承上一个轮廓线的状态{int st=b[now^1][k];LL sum=a[now^1][k];int p=get(st,i);//获得“竖着的”那个插头状态int q=get(st,i+1);//获得“横着的”那个插头状态/*printf("%d %d %lld %d %d\n",u,i,sum,p,q);for (int j=1;j<=3;j++) printf("%d ",get(st,j));printf("\n");*/if (u==n&&i==1)//是起点 {if (p==0&&q==0){change(st,i+1,1);ADD;}else {change(st,i+1,0);ADD;}continue;}if (u==n&&i==m)//终点{if (p>0&&q>0) continue;//不可以有两个插头if (p==0&&q==0) continue;//不可以没有插头 ans=ans+sum;continue;}if (map[u][i]==false)//这个是障碍点{if (p+q==0) ADD;//要是可以把它绕开就好了 continue;}if (p+q==0)//没有连到当前格子{if (map[u][i+1]&&map[u+1][i]){change(st,i,1);change(st,i+1,2);ADD;}}else if (p==0&&q!=0){if (map[u][i+1]==true) ADD;if (map[u+1][i]==true){change(st,i,q);change(st,i+1,0);ADD;}}else if (p!=0&&q==0){if (map[u+1][i]==true) ADD;if (map[u][i+1]==true){change(st,i,0);change(st,i+1,p);ADD;}}else if (p==1&&q==2){if (u==xx&&i==yy)ans+=sum;}else if (p==2&&q==1){change(st,i,0);change(st,i+1,0);ADD;}else if (p==1&&q==1){int top=1;for (int pos=i+2;pos<=m+1;pos++){int temp=get(st,pos);if (temp==1) top++;if (temp==2) top--;if (top==0){change(st,i,0);change(st,i+1,0);change(st,pos,1);ADD;break;}}}else if (p==2&&q==2){int top=1;for (int pos=i-1;pos>0;pos--){int temp=get(st,pos);if (temp==2) top++;if (temp==1) top--;if (top==0){change(st,i,0);change(st,i+1,0);change(st,pos,2);ADD;break;}}}}}for (int i=1;i<=num[now];i++) b[now][i]<<=2;}
}
int main()
{while (true){now=ans=0;memset(map,false,sizeof(map));scanf("%d%d",&n,&m);if (n==0&&m==0) break;for (int u=1;u<=n;u++){char ss[15];scanf("%s",ss+1);for (int i=1;i<=m;i++)if (ss[i]=='.') map[u][i]=true;}xx=n;yy=m;solve();printf("%lld\n",ans);}return 0;
}

另外的做法

我们可以吧图改变一下
吧n+1行建为.##########.
第n+2行建为……………………..
然后跑回路就可以了
这样的话做法比较妙,并且写起来就是模板

  相关解决方案