51nod1683 最短路

因为不能往回走而且行数很少,可以按照列来转移。直接 O(nm) 记录距离显然是不可以的,但是发现相邻格子的最短路最多差 1 ,因此我们只需要记录第1行的最短路和下面每一行和上一行的差【可以用三进制数记录】,每次转移的时候枚举下一列的 01 状态。转移的时候需要 O(n) 求最短路,可以预处理。
复杂度 O(6nm2)

#include<cstdio>
#include<algorithm>
using namespace std;
const int max4=1050,max2=70,maxm=110,maxn=10;
int transx[max4][max2],transy[max4][max2],dp[2][maxm][max4],last[2][maxm][max4],ans[maxm],
f[maxn],tem[maxn],dis[maxn],quex[2][300000],quey[2][300000],n,m,p;
void inc(int &x,int y)
{
    x+=y;
    x=(x>=p?x-p:x);
}
int main()
{
    //freopen("a.in","r",stdin);
    int x,y,x1,y1,ok,tl[2];
    scanf("%d%d%d",&n,&m,&p);
    for (int S=0;S<(1<<(2*(n-1)));S++)
    {
        ok=1;
        for (int i=1;i<n;i++)
        {
            x=(S>>((i-1)*2))&3;
            if (x==2)
            {
                ok=0;
                break;
            }
            if (x)
            {
                if (x==1) f[i]=f[i-1]+1;
                else f[i]=f[i-1]-1;
            }
            else f[i]=f[i-1];
        }
        if (!ok) continue;
        for (int T=0;T<(1<<n);T++)
        {
            for (int i=0;i<n;i++)
                tem[i]=(T>>i)&1;
            dis[0]=f[0]+tem[0];
            for (int i=1;i<n;i++) dis[i]=min(dis[i-1],f[i])+tem[i];
            for (int i=n-2;i>=0;i--)
                dis[i]=min(dis[i],dis[i+1]+tem[i]);
            transy[S][T]=dis[0];
            for (int i=1;i<n;i++)
            {
                if (dis[i]==dis[i-1]) x=0;
                else if (dis[i]==dis[i-1]+1) x=1;
                else x=3;
                transx[S][T]|=x<<(2*(i-1));
            }
        }
    }
    for (int T=0;T<(1<<n);T++)
    {
        x=0;
        for (int i=1;i<n;i++)
            if ((T>>i)&1) x|=1<<(2*(i-1));
        y=T&1;
        if (last[1][y][x]) inc(dp[1][y][x],1);
        else
        {
            last[1][y][x]=1;
            dp[1][y][x]=1;
            tl[1]++;
            quex[1][tl[1]]=x;
            quey[1][tl[1]]=y;
        }
    }
    for (int i=2,c=0;i<=m;i++,c^=1)
    {
        tl[c]=0;
        for (int j=1;j<=tl[c^1];j++)
        {
            x=quex[c^1][j];
            y=quey[c^1][j];
            for (int T=0;T<(1<<n);T++)
            {
                x1=transx[x][T];
                y1=y+transy[x][T];
                if (last[c][y1][x1]==i) inc(dp[c][y1][x1],dp[c^1][y][x]);
                else
                {
                    last[c][y1][x1]=i;
                    dp[c][y1][x1]=dp[c^1][y][x];
                    tl[c]++;
                    quex[c][tl[c]]=x1;
                    quey[c][tl[c]]=y1;
                }
            }
        }
    }
    for (int j=1;j<=tl[m&1];j++)
    {
        x=quex[m&1][j];
        y1=y=quey[m&1][j];
        for (int i=1;i<n;i++)
        {
            x1=(x>>(2*(i-1)))&3;
            if (x1==1) y1++;
            else if (x1==3) y1--;
        }
        inc(ans[y1],dp[m&1][y][x]);
    }
    for (int i=0;i<n+m;i++) printf("%d\n",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值