[51nod1142] 棋子遍历棋盘

该博客讨论了如何解决一个棋子在M*N棋盘上从(1,1)出发遍历所有格子恰好1次并返回的题目。通过分析,提出问题等价于寻找哈密顿回路,并利用插头DP和矩阵乘法进行优化,尤其当N较小而M可能很大时。文章强调了在中间过程中不能转移到特定状态,只有在最后一步才允许。" 133556502,19671095,Vue.js实现滑动切换菜单栏教程,"['Vue', 'JavaScript', '前端框架']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

一个M*N的棋盘, 有一个棋子每次可以向上下左右4个方向中的1个走1步,让这个棋子从(1,1)位置出发,走遍所有格子恰好1次,最后回到(1,1),有多少种不同的走法。由于方案数量巨大,输出数量Mod 10^9 + 7即可。

M≤109
N≤5

分析

题目相当于求一条哈密顿回路,那么可以不用管哪里出发(这个很显然)
发现N很小!很自然地可以往插头DP方面想。用括号序表示插头的联通状态即可。(手算一下发现当n=5,有用的状态只有21个)。
M却很大,但是21 * 21 * 21很小,所以可以矩阵乘法优化。
注意:由于是一条回路,在中间的部分不能转移到插头状态#####,只有最后f[m][n]才能转移到#####,也就是做矩阵乘法求出f[m-1][n],然后最后一行暴力。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=113,mo=1e9+7;

typedef long long LL;

int n,m,p,q,ans,id[799],tot,Sta[25];

struct node
{
    int H[maxn],tot,st[maxn],s[maxn];
    void init()
    {
        memset(H,255,sizeof(H));
        tot=0;
    }
    void add(int S,int v)
    {
        int i=S%maxn;
        for (;H[i]>=0 && st[H[i]]!=S;i=(i+1)%maxn);
        if (H[i]<0)
        {
            st[tot]=S; s[tot]=v; H[i]=tot++;
        }else s[H[i]]=(s[H[i]]+v)%mo;
    }
}f[2];

struct Matrix
{
    int a[25][25];
}a,b,c;

Matrix operator * (const Matrix &a,const Matrix &b)
{
    memset(c.a,0,sizeof(c.a));
    for (int i=0;i<tot;i++)
    {
        for (int j=0;j<tot;j++)
        {
            for (int k=0;k<tot;k++) c.a[i][j]=(c.a[i][j]+(LL)a.a[i][k]*b.a[k][j])%mo;
        }
    }
    return c;
}

int Get(int s,int w)
{
    return (s>>(w<<1))&3;
}

void Set(int &s,int w,int v)
{
    s^=Get(s,w)<<(w<<1);
    s^=v<<(w<<1);
}

void dp(int m)
{
    p=0; q=1;
    f[0].init();
    if (m>=0) f[0].add(m,1);else
    {
        for (int i=0;i<tot;i++) f[0].add(Sta[i],b.a[0][i]);
    }
    for (int i=0;i<n;i++,p^=1,q^=1)
    {
        f[q].init();
        for (int j=0;j<f[p].tot;j++)
        {
            int st=f[p].st[j],s=f[p].s[j],x=Get(st,i),y=Get(st,i+1);
            if (!x)
            {
                if (!y)
                {
                    if (i==n-1) continue;
                    Set(st,i,1); Set(st,i+1,2);
                    f[q].add(st,s);
                }else
                {
                    if (i<n-1) f[q].add(st,s);
                    Set(st,i,y); Set(st,i+1,0);
                    f[q].add(st,s);
                }
            }else
            {
                if (!y)
                {
                    f[q].add(st,s);
                    if (i==n-1) continue;
                    Set(st,i,0); Set(st,i+1,x);
                    f[q].add(st,s);
                }else
                {
                    Set(st,i,0); Set(st,i+1,0);
                    if (x==1)
                    {
                        if (y==1)
                        {
                            for (int k=i+2,t=1;;k++)
                            {
                                if (Get(st,k)==1) t++;
                                else if (Get(st,k)==2)
                                {
                                    t--;
                                    if (!t)
                                    {
                                        Set(st,k,1); break;
                                    }
                                }
                            }
                            f[q].add(st,s);
                        }else if (m<0 && i==n-1) f[q].add(st,s);
                    }else
                    {
                        if (y==1) f[q].add(st,s);else
                        {
                            for (int k=i-1,t=1;;k--)
                            {
                                if (Get(st,k)==2) t++;
                                else if (Get(st,k)==1)
                                {
                                    t--;
                                    if (!t)
                                    {
                                        Set(st,k,2); break;
                                    }
                                }
                            }
                            f[q].add(st,s);
                        }
                    }
                }
            }
        }
    }
    for (int i=0;i<f[p].tot;i++) f[p].st[i]<<=2;
}

void Find(int x,int y,int st)
{
    if (x>n)
    {
        if (!y) id[st]=tot,Sta[tot++]=st;
        return;
    }
    Find(x+1,y,st);
    Find(x+1,y+1,st+(1<<(x<<1)));
    if (y>0) Find(x+1,y-1,st+(2<<(x<<1)));
}

void quick(int x)
{
    if (!x) return;
    quick(x>>1);
    b=b*b;
    if (x&1) b=b*a;
}

int main()
{
    scanf("%d%d",&m,&n);
    memset(id,255,sizeof(id));
    Find(1,0,0);
    for (int i=0;i<tot;i++)
    {
        dp(Sta[i]);
        for (int j=0;j<f[p].tot;j++) if (id[f[p].st[j]]>0) a.a[i][id[f[p].st[j]]]=f[p].s[j];
        b.a[i][i]=1;
    }
    quick(m-1);
    dp(-1);
    for (int i=0;i<f[p].tot;i++) if (f[p].st[i]==0)
    {
        printf("%d\n",(f[p].s[i])*2%mo); return 0;
    }
    printf("0\n");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值