插头dp

对于网格中的dp可以用轮廓线,如果有一些格子不能走就可以用插头dp了。

bzoj2331 地板

题目大意:用L型铺地n*m,有一些格子不能铺,求方案数。

思路:fi[i][j][s]表示铺到(i,j),轮廓线状态s,0表示没有插头,1表示插头没拐弯,2表示插头拐弯了,手动枚举转移。

注意:(1)按四进制好写;

   (2)因为实际状态和四进制的差很多,所以用hash表来存储,防止mle和tle,同时使用滚动数组。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 105
#define M 200000
#define uu 50000
#define pp 20110520
using namespace std;
int ai[N][N],fi[2][M]={0},point[uu],next[M],en[M],ha[2][M],tot[2],bt[N],
    nm,n,m,cur,la;
int in(){
    char ch=getchar();
    while(ch!=‘_‘&&ch!=‘*‘) ch=getchar();
    return ch==‘*‘;}
void add(int s,int gi){
    int i,x=s%uu;
    for (i=point[x];i;i=next[i])
        if (ha[cur][en[i]]==s){
            fi[cur][en[i]]=(fi[cur][en[i]]+gi)%pp;
            return;
        }
    ha[cur][++tot[cur]]=s;fi[cur][tot[cur]]=gi;
    next[++nm]=point[x];point[x]=nm;en[nm]=tot[cur];}
void dp(){
    int i,j,k,p,q,s,gi,ji;cur=1;la=0;
    tot[cur]=1;en[1]=0;ji=3;
    ha[cur][1]=0;fi[cur][1]=1;
    for (i=0;i<N;++i) bt[i]=i<<1;
    for (i=1;i<=n;++i){
        for (j=1;j<=tot[cur];++j) ha[cur][j]=(ha[cur][j]<<2)&((1<<(bt[m+1]+1))-1);
        for (j=1;j<=m;++j){
            cur^=1;la^=1;
            tot[cur]=nm=0;
            memset(fi[cur],0,sizeof(fi[cur]));
            memset(point,0,sizeof(point));
            for (k=tot[la];k;--k){
                s=ha[la][k];gi=fi[la][k];
                if (!gi) continue;
                p=(s>>bt[j-1])&ji;
                q=(s>>bt[j])&ji;
                if (ai[i][j]){
                    if (!p&&!q) add(s,gi);
                }else if (!p&&!q){
                    if (!ai[i+1][j]) add(s+(1<<bt[j-1]),gi);
                    if (!ai[i][j+1]) add(s+(1<<bt[j]),gi);
                    if (!ai[i+1][j]&&!ai[i][j+1]) add(s+(1<<(bt[j-1]+1))+(1<<(bt[j]+1)),gi);
                }else if (!p){
                    s-=(1<<bt[j])*q;
                    if (q==1){
                        if (!ai[i][j+1]) add(s+(1<<(bt[j]+1)),gi);
                        if (!ai[i+1][j]) add(s+(1<<bt[j-1]),gi);
                    }else{
                        add(s,gi);
                        if (!ai[i+1][j]) add(s+(1<<(bt[j-1]+1)),gi);
                    }
                }else if (!q){
                    s-=(1<<bt[j-1])*p;
                    if (p==1){
                        if (!ai[i][j+1]) add(s+(1<<bt[j]),gi);
                        if (!ai[i+1][j]) add(s+(1<<(bt[j-1]+1)),gi);
                    }else{
                        add(s,gi);
                        if (!ai[i][j+1]) add(s+(1<<(bt[j]+1)),gi);
                    }
                }else if (p==1&&q==1){
                    s-=(1<<bt[j-1])+(1<<bt[j]);
                    add(s,gi);
                }
            }
        }
    }
}
int main(){
    int i,j;scanf("%d%d",&n,&m);
    memset(ai,127,sizeof(ai));
    if (n<m){
        for (i=1;i<=n;++i)
            for (j=1;j<=m;++j) ai[j][i]=in();
        swap(n,m);
    }else
        for (i=1;i<=n;++i)
            for (j=1;j<=m;++j) ai[i][j]=in();
    dp();printf("%d\n",fi[cur][1]);
}

时间: 2024-10-27 12:35:18

插头dp的相关文章

[入门向选讲] 插头DP:从零概念到入门 (例题:HDU1693 COGS1283 BZOJ2310 BZOJ2331)

转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.html 最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型…… 每一道题的状态都不一样,并且有不少的分类讨论,让插头DP十分锻炼思维的全面性和严谨性. 下面我们一起来学习插头DP的内容吧! 插头DP主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有很多种,并且一般数据规模较小. 由于棋盘有很特殊的结构,使得它可以与“连通性”有很强的联系,因此插头DP最常见的应用要数

插头DP学习

队内没人会插头DP,感觉这个不会不行...所以我还是默默去学了一下, 学了一天,感觉会了一点.对于每一行,一共有j+1个插头,如果是多回路类的题目, 比较简单,可以用1表示有插头,0表示没有插头,这样就可以愉快转移了, 对于当前出来的位置(i,j),与它有关的插头有j-1和j 那么我们可以枚举状态经行转移. 对于单回路的问题....只是了解思想,目前还不会写,太笨了=_= poj 2411 Mondriaan's Dream 题意:用1*2小方块组成n*m矩阵有多少种组成方式 思路:我们从上到下

BZOJ 2595 游览计划(插头DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2595 题意:给出一个数字矩阵.求一个连通块包含所有的数字0且连通块内所有数字之和最小. 思路:对于每个格子,是0则必须要选.那么对于不选的格子(i,j)在什么时候可以不选呢?必须同时满足以下两个条件: (1)(i,j)不是0: (2)(i-1,j)不选或者(i-1,j)选了但是轮廓线上还有别的地方与(i-1,j)是一个连通块. int Pre[105][N],op[105][N]; s

【插头dp】CDOJ1690 这是一道比CCCC简单题难的简单题

最裸的插头dp,可参见大白书. #include<cstdio> #include<cstring> using namespace std; #define MOD 1000000007 int f[2][(1<<5)+10],n,m; int main(){ scanf("%d%d",&n,&m); int cur=0; f[0][(1<<m)-1]=1; for(int i=0;i<n;++i){ for(in

POJ 2411 Mondriaan&#39;s Dream ——状压DP 插头DP

[题目分析] 用1*2的牌铺满n*m的格子. 刚开始用到动规想写一个n*m*2^m,写了半天才知道会有重复的情况. So Sad. 然后想到数据范围这么小,爆搜好了.于是把每一种状态对应的转移都搜了出来. 加了点优(gou)化(pi),然后poj上1244ms垫底. 大概的方法就是考虑每一层横着放的情况,剩下的必须竖起来的情况到下一层取反即可. 然后看了 <插头DP-从入门到跳楼> 这篇博客,怒抄插头DP 然后16ms了,自己慢慢YY了一下,写出了风(gou)流(pi)倜(bu)傥(tong)

HDU 4113 Construct the Great Wall(插头dp)

好久没做插头dp的样子,一开始以为这题是插头,状压,插头,状压,插头,状压,插头,状压,无限对又错. 昨天看到的这题. 百度之后发现没有人发题解,hust也没,hdu也没discuss...在acm-icpc信息站发现难得的一篇题解.不过看到是插头二字之后,代码由于风格太不一样就没看了,自己想了好久,想通了.然后就等到今天才码.... 如果把点看成网格,那就可以实现,没有公共点公共边等限定条件,也显然是插头dp的最短单回路的模型.这是本题的一个难点(当时想到这样是因为,题目要求计算最短周长,显然

BZOJ 2331 地板(插头DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2331 题意:给出一个n*m的地面.有些是障碍.用L型的地板砖铺满.有多少种方案. 思路:用0表示没有插头,用1表示有插头且可以拐弯,用3表示有插头但是不能再拐弯了. 设有m列,轮廓线为[0,m].对于格子(i,j),设左插头x上插头y,那么转移有: (1)x=0,y=0:此时如图1-0,有三种转移,分别是1-1,1-2,1-3; (2)x!=0,y!=0:此时只有当x=y=1时可以转移

【GDKOI 2016】地图 map 类插头DP

Description 对于一个n*m的地图,每个格子有五种可能:平地,障碍物,出口,入口和神器.一个有效的地图必须满足下列条件: 1.入口,出口和神器都有且仅出现一次,并且不在同一个格子内. 2.入口,出口和神器两两都是连通的. 连通性判断为四连通. 现在给出一个n*m的地图,其中一些格子的状态已经确定,另一些格子的状态未确定. 问当所有的格子状态确定之后,有多少种情况使得该地图是一个有效的地图?输出结构为答案模1e9+7. Input 第一行输入两个整数n和m,意义如题目所示.接下来n行,每

HDU4084 插头dp

题意:给定一个图,0是不能放的,然后现在有1X1和1X2方块,最后铺满该图,使得1X1使用次数在C到D之间,1X2次数随便,问有几种放法 思路:插头DP或轮廓线,多加一维DP讨论就可以 注意插头DP状态转移的是轮廓线,从左上开始处理,具体见代码注释 #include <bits/stdc++.h> using namespace std; typedef long long ll; #define CLR(x, v) memset(x, v, sizeof x); #define PI(x)