学习笔记:插头DP(BZOJ2331: [SCOI2011]地板)

膜了一波WXH天犇的代码,woc,惊为天人,代码还能这么写的!?

插头DP

  • 插头DP的状压是要把整行的插头情况全部状压进去,所以说插头DP也是状压DP的一种(不要尝试不压缩)
  • 插头DP大多逐格递推,当前格仅需要上一格递推,所以可以滚动。
  • 插头DP是插在边上的插头,不是点上,因此有m个下插头以及一个右插头,注意空间。
  • 插头DP的关键在于确定插头的种类(包括没有插头),很多时候会超过2种也就是说不- - 能用二进制来压位固然简单,不能还不是当做对应进制压位。
  • 插头DP的时间复杂度是 nmKm n ∗ m ∗ K m ,所以要通过翻转使得m总是不比n大,其中K是插头种类数量(标号:0~K-1,其中0是代表没有插头)。

滚动数组的技巧:

  • 这里一般来说都要滚动的,用一个cur|cur=0或1,代表已经填完了的那一格子填在了哪一排,每填完一格cur^1
  • 我们转移的时候实际上用的是刷表法,K枚举的实际上是上一排的情况,来刷这一排的情况。
  • 其中还有一格比较重要的点就是每次填当前表的时候都要memset(dp[cur^1]),不清空没法刷表
  • 最终答案一般就是dp[cur][0]

压位的技巧:

  • 很多压2的幂次位然后使劲hash,WXH表示这很蠢直接写K进制即可,类比二进制/十进制都可以
  • 具体操作是用一个table数组,table[i]表示i^K

注意:以下我们说的存储全部是基于右边是低位,左边是高位的写数意识,最右边是第一位。

  • 我们把第一位用来存右插头位置,这样不会涉及到位运算,非常方便。
  • 然后table[j]就是第j个格子在进制中对应的基本单位了,比如说005000,那么table[3]就是1000,刚好就是第三个格子的值(因为第一位是用右插头表示的,所以实际是第四位)
  • 要提取第j位的元素就是S/table[j]%K; 有插头的元素是s%K;

转移的技巧:

  • 讨论的时候如果有BASE个插头(算上没有插头),那么久老老实实讨论Base^2种情况,其中有些情况无法转移,所以情况会减少一点。
  • 其中还要讨论左端点的特殊情况,对于右插头不是空的情况基本可以continue,在刷表的时候当前元素没有贡献也可以continue
  • 枚举插头情况的时候要枚举到table[m+1]-1

一个代码(BZOJ 2331: [SCOI2011]地板)

#include<bits/stdc++.h>
using namespace std;
const int maxn=105,maxv=177147+105,mod=20110520;
int n,m,to;//要保证m比较小;
char op[maxn];
char s[maxn][maxn];
int dp[maxn][maxv],table[maxn],cur;
inline void upd(int &x,int y)
{
    x+=y;
    if(x>=mod)x-=mod;
}
void Init()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",op+1);
        for(int j=1;j<=m;j++)
        {
            if(n>m)
                s[i][j]=op[j];
            else
                s[j][i]=op[j];
        }
    }
    if(m>n)swap(n,m);
    table[0]=1;
    for(int i=1;i<=m+1;i++)table[i]=table[i-1]*3;
}
void MakeDp()
{
    cur=0;
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++,cur^=1)
        {
            memset( dp[ cur^1 ], 0, sizeof (dp[ cur^1 ]) );  
            for(int k=0;k<table[m+1];k++)if(dp[cur][k])
            {
                int left=k%3,up=k/table[j]%3;
                int t=dp[cur][k];
                if(s[i][j]=='*')
                {
                    if(!left && !up)upd(dp[cur^1][k],t);
                    continue;
                }
                if(j==1 && left)
                    continue;
                if(!left)
                {
                    if(!up)
                    {
                        upd(dp[cur^1][k+1],t);
                        upd(dp[cur^1][k+table[j]*2+2],t);
                        upd(dp[cur^1][k+table[j]],t);
                    }
                    else if(up==1)
                    {
                        upd(dp[cur^1][k],t);
                        upd(dp[cur^1][k-table[j]+2],t);
                    }
                    else
                    {
                        upd(dp[cur^1][k],t);
                        upd(dp[cur^1][k-table[j]*2],t);
                    }
                }
                else if(left==1)
                {
                    if(!up)
                    {
                        upd(dp[cur^1][k+table[j]*2-1],t);
                        upd(dp[cur^1][k],t);
                    }
                    else if(up==1)
                    {
                        upd(dp[cur^1][k-table[j]-1],t);
                    }
                }
                else
                {
                    if(!up)
                    {
                        upd(dp[cur^1][k],t);
                        upd(dp[cur^1][k-2],t);
                    }
                }
            }
        }
    }
    printf("%d\n",dp[cur][0]);
}
int main()
{
    freopen("in.txt","r",stdin);
    Init();
    MakeDp();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值