LOJ6301:「CodePlus 2018 3 月赛」白金元首与莫斯科 (类插头DP)

本文介绍了一种在特定条件下优化状态压缩动态规划(状压DP)的方法,通过单格子转移而非按行转移来减少时间开销。文章分享了一个具体的算法实现案例,并详细解释了如何通过位运算和分类讨论来实现O(nm2^m)的时间复杂度。
摘要由CSDN通过智能技术生成

题目传送门:https://loj.ac/problem/6301


题目分析:比赛的时候乱写了个 O(n2m3m) O ( n 2 m 3 m ) 的状压DP,结果只拿了14pts。当时我还在想怎么才能拿第一个点的那10pts,要不要再写一个另外的算法。后来发现由于 n<m n < m ,只需要交换 n,m n , m ,稍微处理一下就好了QAQ。赛后30min发现自己还能写 O(n3m) O ( n 3 m ) 的方法,于是补了一发,然而卡不过 n=15 n = 15 的点……

其实我之前写的状压DP都是按行转移的。插头DP可以单格子转移但因为太难写我就弃了,这题比较像状压才去写了一下(依然比较难写)。这题要按行转移就必须枚举子集,时间开销太高。加上题目中只令一个格子变为障碍物这样的特殊条件,还是一个一个格子转移更优。如果在单格子(i,j)处转移的话,状压的就是(i,j)左上方的一条轮廓线,这条轮廓线同样只需要用0/1记录有没有下插头即可。而(i,j)处有两种插头(左和下),所以要加多一位,总状态数变为 2m+1 2 m + 1 。转移的时候通过分类讨论和一些位运算,即可做到 O(nm2m) O ( n m 2 m )

按地图正反都做一遍这个DP,然后枚举答案,将两边的状态对应起来,同样是这个时间复杂度。注意加一些底层优化。总之本机测试不开O2优化最慢的点要跑3.8s,交上LOJ开了O2后只要900多msOwO。

代码奇长系列


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=22;
const int maxs=(1<<18)+100;
const int M=1000000007;

int f[maxn][maxn][maxs];
int g[maxn][maxn][maxs];

int map[maxn][maxn];
int n,m;

int Get(int x)
{
    return (1<<(x-1));
}

int Mod(int x)
{
    if (x>=M) return x-M;
    return x;
}

int main()
{
    freopen("C.in","r",stdin);
    freopen("C.out","w",stdout);

    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++)
        for (int j=1; j<=m; j++) scanf("%d",&map[i][j]);
    int ms=(1<<(m+1));

    f[1][1][0]=1;
    if (!map[1][1]) f[1][1][1]=1,f[1][1][2]=1;
    for (int i=1; i<=n; i++)
        for (int j=1; j<=m; j++) if ( i<n || j<m )
            for (int s=0; s<ms; s++) if (f[i][j][s])
            {
                int x=(s&Get(j+1));
                int y=(s&Get(j+2));
                if ( (x&&y) || ( (x||y) && map[i][j+1] ) ) continue;

                if (j<m)
                    if (map[i][j+1])
                    {
                        int &v=f[i][j+1][s];
                        v=Mod(v+f[i][j][s]);
                    }
                    else
                    {
                        if (x)
                        {
                            int &v=f[i][j+1][s^Get(j+1)];
                            v=Mod(v+f[i][j][s]);
                        }
                        if (y)
                        {
                            int &v=f[i][j+1][s^Get(j+2)];
                            v=Mod(v+f[i][j][s]);
                        }
                        if ( !x && !y )
                        {
                            int num=f[i][j][s];
                            int &p=f[i][j+1][s];
                            p=Mod(p+num);
                            int &q=f[i][j+1][s|Get(j+1)];
                            q=Mod(q+num);
                            int &r=f[i][j+1][s|Get(j+2)];
                            r=Mod(r+num);
                        }
                    }
                else
                {
                    if (x) continue;
                    y=(s&1);
                    if ( y && map[i+1][1] ) continue;

                    if (map[i+1][1])
                    {
                        int &v=f[i+1][1][s<<1];
                        v=Mod(v+f[i][j][s]);
                    }
                    else
                        if (y)
                        {
                            int &v=f[i+1][1][(s^1)<<1];
                            v=Mod(v+f[i][j][s]);
                        }
                        else
                        {
                            int num=f[i][j][s],t=(s<<1);
                            int &p=f[i+1][1][t];
                            p=Mod(p+num);
                            int &q=f[i+1][1][t|1];
                            q=Mod(q+num);
                            int &r=f[i+1][1][t|2];
                            r=Mod(r+num);
                        }
                }
            }

    //printf("%d\n",f[n][m][0]);

    g[n][m][0]=1;
    if (!map[n][m]) g[n][m][Get(m+1)]=1,g[n][m][Get(m)]=1;
    for (int i=n; i>=1; i--)
        for (int j=m; j>=1; j--) if ( i>1 || j>1 )
            for (int s=0; s<ms; s++) if (g[i][j][s])
            {
                if (j>1)
                {
                    int x=(s&Get(j));
                    int y=(s&Get(j-1));
                    if ( (x&&y) || ( (x||y) && map[i][j-1] ) ) continue;

                    if (map[i][j-1])
                    {
                        int &v=g[i][j-1][s];
                        v=Mod(v+g[i][j][s]);
                    }
                    else
                    {
                        if (x)
                        {
                            int &v=g[i][j-1][s^Get(j)];
                            v=Mod(v+g[i][j][s]);
                        }
                        if (y)
                        {
                            int &v=g[i][j-1][s^Get(j-1)];
                            v=Mod(v+g[i][j][s]);
                        }
                        if ( !x && !y )
                        {
                            int num=g[i][j][s];
                            int &p=g[i][j-1][s];
                            p=Mod(p+num);
                            int &q=g[i][j-1][s|Get(j-1)];
                            q=Mod(q+num);
                            int &r=g[i][j-1][s|Get(j)];
                            r=Mod(r+num);
                        }
                    }
                }
                else
                {
                    if (s&1) continue;
                    int y=(s&Get(m+1));
                    if ( y && map[i-1][m] ) continue;

                    if (map[i-1][m])
                    {
                        int &v=g[i-1][m][s>>1];
                        v=Mod(v+g[i][j][s]);
                    }
                    else
                        if (y)
                        {
                            int &v=g[i-1][m][(s^Get(m+1))>>1];
                            v=Mod(v+g[i][j][s]);
                        }
                        else
                        {
                            int num=g[i][j][s],t=(s>>1);
                            int &p=g[i-1][m][t];
                            p=Mod(p+num);
                            int &q=g[i-1][m][t|Get(m)];
                            q=Mod(q+num);
                            int &r=g[i-1][m][t|Get(m+1)];
                            r=Mod(r+num);
                        }
                }
            }

    //printf("%d\n",g[1][1][0]);

    f[0][m][0]=1;
    for (int i=0; i<n; i++)
        for (int s=0; s<ms; s++) if (!(s&Get(m+1)))
            f[i+1][0][s<<1]=f[i][m][s];
    g[n+1][1][0]=1;
    for (int i=n+1; i>1; i--)
        for (int s=0; s<ms; s++) if (!(s&1))
            g[i-1][m+1][s>>1]=g[i][1][s];

    for (int i=1; i<=n; i++)
    {
        for (int j=1; j<=m; j++)
            if (map[i][j]) printf("0 ");
            else
            {
                int ans=0;
                for (int s=0; s<ms; s++)
                    if ( !(s&Get(j)) && !(s&Get(j+1)) )
                        ans=Mod(ans+ (long long)f[i][j-1][s]*g[i][j+1][s]%M );
                printf("%d ",ans);
            }
        printf("\n");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值