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

【题目描述】
给你一个 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行建为……………………..
然后跑回路就可以了
这样的话做法比较妙,并且写起来就是模板

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值