Ural 1519 Formula 1

题意:找出N*M大小的带障碍格子的哈密顿回路总数。

题解:插头DP进阶题,也是入门题,插头DP的详解还是看《基于连通性状态压缩的动态规划问题》论文吧。

12列共13个插头,一共有3种插头,3进制可解,但用位操作处理2进制更容易,所以设成4进制,2位代表1个插头的状态,用括号表示法即 00:无插头;01:'(';10:‘)’,26位长度的状态是开不了的,所以用hash表进行优化处理,我用一个head指针指向表中任意一个,便于遍历整个hash表。

对每个格子的情况我进行了下面分类。

首先,如果该格子有障碍,那么只能是没有插头的状态才能继续向下转移。

反之,如果只有一个插头,那么也比较容易处理,直接转移成两种状态即可。较为麻烦的是有两个插头,这涉及到合并操作,由于第一次写,所以分了四小类(本质上只有两类):

1、左插头与上插头属于同一条路径,即括号状态中两个插头对应的括号是互相匹配的,又由于左插头与上插头是轮廓线相邻两个,那么一旦合并,意味着形成回路,也就是只有最右下角的非障碍格子才有可能(进行一次特判)。另外,这里判断两括号是否匹配也无需搜索,只需左插头是左括号,上插头是右括号即可(因为路径不交叉)。

2、如果左插头是右括号,上插头是左括号,连接后两括号消失,原来各自代表的路径连成一条,且原左插头的右括号所对应的左括号仍旧是新路径的左括号。

3、如果左插头与上插头均是左括号,那么连接后,会使得原来左插头所代表的左括号右移至上插头所代表的左括号匹配的右括号上,只需将该右括号改为左括号,即在右括号的位置异或上11B即可。

4、如果均为右括号,那么和3一样,不同的只是将上插头所代表的右括号移动到左插头对应的左括号去而已。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAX=100007;
typedef long long LL;
struct HT
{
    int head,hash[MAX],nxt[MAX];
    LL cnt[MAX];
    void init()
    {
        head=-1;
        memset(hash,-1,sizeof(hash));
        memset(nxt,-1,sizeof(nxt));
        memset(cnt,0,sizeof(cnt));
    }
    void insert(int st,LL ct)
    {
        LL pos=st%MAX;
        LL ad=1;
        while(hash[pos]!=st&&hash[pos]!=-1)
        {
            pos+=ad*ad;
            ad++;
            if(pos>=MAX)pos%=MAX;
        }
        if(hash[pos]==-1)
        {
            hash[pos]=st;
            cnt[pos]=ct;
            nxt[pos]=head;
            head=pos;
        }
        else
            cnt[pos]+=ct;
    }
} dp[2];
int n,m;
char mp[14][14];
int findright(int st,int pos)
{
    int tp,p=pos;
    for(int dep=1; dep!=0; p+=1)
    {
        tp=((st>>((m-p+1)*2))&3)%4;
        switch(tp)
        {
        case 0:
            break;
        case 1:
            dep++;
            break;
        case 2:
            dep--;
            break;
        }
        if(dep==0)
            break;
    }
    return 3<<((m-p+1)*2);
}
int findleft(int st,int pos)
{
    int tp,p=pos;
    for(int dep=1; dep!=0; p-=1)
    {
        tp=((st>>((m-p+1)*2))&3)%4;
        switch(tp)
        {
        case 0:
            break;
        case 1:
            dep--;
            break;
        case 2:
            dep++;
            break;
        }
        if(dep==0)
            break;
    }
    return 3<<((m-p+1)*2);
}
LL getans(int fg)
{
    LL pos=0,ad=1;
    while(dp[fg].hash[pos]!=0&&dp[fg].hash[pos]!=-1)
    {
        pos+=ad*ad;
        ad++;
        if(pos>=MAX)pos%=MAX;
    }
    if(dp[fg].hash[pos]==-1)
    {
        return 0ll;
    }
    else
        return dp[fg].cnt[pos];
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1; i<=n; i++)scanf(" %s",mp[i]+1);
        int fg1=1,fg2,ex,ey;
        for(ex=n; ex>=1; ex--)
        {
            for(ey=m; ey>=1; ey--)
            {
                if(mp[ex][ey]=='.')
                {
                    fg1=0;
                    break;
                }
            }
            if(!fg1)
                break;
        }
        if(fg1)
        {
            printf("0\n");
            continue;
        }
        fg1=0,fg2=1;
        dp[fg2].init();
        dp[fg2].insert(0,1ll);
        for(int i=1; i<=ex; i++)
        {
            fg1=!fg1;
            fg2=!fg2;
            dp[fg2].init();
            for(int tp=dp[fg1].head; tp!=-1; tp=dp[fg1].nxt[tp])
            {
                if(dp[fg1].hash[tp]&3)
                    continue;
                dp[fg2].insert(dp[fg1].hash[tp]>>2,dp[fg1].cnt[tp]);
            }
            for(int j=0; j<m; j++)
            {
                fg1=!fg1;
                fg2=!fg2;
                dp[fg2].init();
                int a=3<<(2*(m-j)),b=3<<(2*(m-j-1));
                for(int pos=dp[fg1].head; pos!=-1; pos=dp[fg1].nxt[pos])
                {
                    int st=dp[fg1].hash[pos];
                    LL ct=dp[fg1].cnt[pos];
                    int x=((st&a)>>(2*(m-j)))%4,y=((st&b)>>(2*(m-j-1)))%4;
                    if(mp[i][j+1]=='*')
                    {
                        if(x||y)
                            continue;
                        dp[fg2].insert(st,ct);
                    }
                    else if(x)
                    {
                        if(y)
                        {
                            if(x==1&&y==1)
                                dp[fg2].insert((st&(~a)&(~b))^findright(st,j+3),ct);
                            else if(x==2&&y==1)
                                dp[fg2].insert(st&(~a)&(~b),ct);
                            else if(x==2&&y==2)
                                dp[fg2].insert((st&(~a)&(~b))^findleft(st,j),ct);
                            else if(x==1&&y==2&&i==ex&&j==ey-1)
                                dp[fg2].insert(st&(~a)&(~b),ct);
                        }
                        else
                        {
                            dp[fg2].insert(st,ct);
                            dp[fg2].insert(st&(~a)|(x<<(2*(m-j-1))),ct);
                        }
                    }
                    else if(y)
                    {
                        dp[fg2].insert(st,ct);
                        dp[fg2].insert(st&(~b)|(y<<(2*(m-j))),ct);
                    }
                    else
                        dp[fg2].insert(st|(1<<(2*(m-j)))|(2<<(2*(m-j-1))),ct);
                }
            }
        }
        printf("%lld\n",getans(fg2));
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值