插头DP学习

队内没人会插头DP,感觉这个不会不行。。。所以我还是默默去学了一下,

学了一天,感觉会了一点。对于每一行,一共有j+1个插头,如果是多回路类的题目,

比较简单,可以用1表示有插头,0表示没有插头,这样就可以愉快转移了,

对于当前出来的位置(i,j),与它有关的插头有j-1和j 那么我们可以枚举状态经行转移。

对于单回路的问题。。。。只是了解思想,目前还不会写,太笨了=_=

poj 2411 Mondriaan‘s Dream

题意:用1*2小方块组成n*m矩阵有多少种组成方式

思路:我们从上到下处理每一行,对于当前处理的位置 (i,j),枚举它的插头状态,0~(1<<(m+1));

假设状态是 sta,如果sta表示的状态没有j-1和j 插头(即(sta&(1<<(j-1))) == 0 &&(sta&(1<<j)) == 0)

那么转移到它的状态有两种,j-1插头有或者j插头存在,

如果sta表示的状态都有j-1和j 插头,这样状态是不合法的,直接=0

如果sta表示的状态j 插头,表示是平放方块,这个状态只能由j-1,j插头不存在状态转移过来

如果sta表示的状态j-1 插头,表示是竖放方块,这个状态只能由j-1,j插头不存在状态转移过来

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<ctime>
#include<bitset>
#define LL long long
#define INF 999999
#define maxn 200001
#define mod 1000000007
#define INF 0x3f3f3f3f
using namespace std;

int mat[15][15];
LL dp[12][12][(1<<13)+10] ;

int main()
{
    int i , n ,m , j, k ,case1=0;
    int len , T ,x ,y ,sta ;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n+m==0)break ;
        if((n*m)&1)
        {
            puts("0") ;
            continue ;
        }
        memset(dp,0,sizeof(dp)) ;
        len=(1<<(m+1));
        dp[0][m][0]=1;
        for( i = 1 ; i <= n ;i++)
        {
            for( j = 0 ; j < len ;j++)
                dp[i][0][(j<<1)]=dp[i-1][m][j] ;
            for( k = 1 ; k <= m ;k++)
            {
                for( sta = 0 ; sta < len ;sta++)
                {
                    y = (1<<k) ;
                    x = (1<<(k-1)) ;
                    if((sta&x) != 0 &&(sta&y) != 0 )
                    {
                        dp[i][k][sta]=0;
                    }
                    else if((sta&x)==0&&0==(sta&y))
                    {
                        dp[i][k][sta]=dp[i][k-1][sta^x]+dp[i][k-1][sta^y] ;
                    }
                    else if((sta&x))
                        dp[i][k][sta]=dp[i][k-1][sta^x];
                    else dp[i][k][sta]=dp[i][k-1][sta^y];
                }
            }
        }
        printf("%I64d\n",dp[n][m][0]);
    }
    return 0 ;
}

hdu 4804 Campus Design

题意:和上题差不多,多了的东西是,有些地方不能放方块,

还有可以放1*1方块,求的是 1*1放的个数限制在c-d内的放的方式有多少种

思路:具体做法和上面也是差不多,

差别:对于1*1放置个数的限制,我们可以开多一维记录1*1用的个数,

对于不能放置的位置(i,j ),合法的状态sta,里面不能有 j-1,j 插头

对于可以放置的位置(1,j) ,状态是 sta,如果sta表示的状态没有j-1和j 插头时

多了一步转移,就是 放置1*1的状态。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<ctime>
#include<bitset>
#define LL long long
#define INF 999999
#define maxn 200001
#define mod 1000000007
#define INF 0x3f3f3f3f
using namespace std;

char mat[105][25];
int dp[2][12][22][(1<<12)+10];

int main()
{
    int i , n ,m , j, k ,case1=0;
    int len , T ,x ,y ,sta,c,d ,v ;
    int now,pre;
    while(scanf("%d%d%d%d",&n,&m,&c,&d )!=EOF)
    {
        for( i = 1 ; i <= n ;i++)
           scanf("%s",mat[i]+1);
        memset(dp,0,sizeof(dp)) ;
        len=(1<<(m+1));
        dp[0][m][0][0]=1;
        pre=1;
        now=0;
        for( i = 1 ; i <= n ;i++)
        {
            swap(pre,now);
          //  memset(dp[now],0,sizeof(dp[now])) ;
            for( v = 0 ; v <= d ;v++ )
               for( j = 0 ; j < len ;j++)
                 dp[now][0][v][(j<<1)]=dp[pre][m][v][j] ;
            for( k = 1 ; k <= m ;k++)
            {
                y = (1<<k) ;
                x = (1<<(k-1)) ;
                for( v = 0 ; v <= d ;v++ )
                    for( sta = 0 ; sta < len ;sta++)
                    {
                             if(mat[i][k]==‘0‘)
                             {
                                 if((sta&x)||(sta&y))
                                 {
                                     dp[now][k][v][sta]=0;
                                 }
                                 else dp[now][k][v][sta]=dp[now][k-1][v][sta] ;
                             }
                             else{
                                if((sta&x) != 0 &&(sta&y) != 0 )
                                {
                                    dp[now][k][v][sta]=0;
                                }
                                else if((sta&x) == 0 &&(sta&y) == 0 )
                                {
                                    dp[now][k][v][sta]=(dp[now][k-1][v][sta^x]+dp[now][k-1][v][sta^y])%mod ;
                                    if(v>0)dp[now][k][v][sta]=(dp[now][k][v][sta]+dp[now][k-1][v-1][sta])%mod ;
                                }
                                else if((sta&x))
                                  dp[now][k][v][sta]=dp[now][k-1][v][sta^x];
                                else dp[now][k][v][sta]=dp[now][k-1][v][sta^y];
                             }
                    }
            }
        }
        int ans1=0;
        for( i = c ; i <= d;i++)
            ans1=(ans1+dp[now][m][i][0])%mod;
        printf("%d\n",ans1);
    }
    return 0 ;
}

hdu 1693Eat the Trees

题意:在n*m的矩阵中,有些格子有树,没有树的格子不能到达,找一条或多条回路,吃完所有的树,求有多少中方法。

思路:看这个博客吧,here

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#include<stack>
#include<map>
#include<ctime>
#include<bitset>
#define LL long long
#define INF 999999
#define maxn 200001
#define mod 1000000007
#define INF 0x3f3f3f3f
using namespace std;

int mat[15][15];
LL dp[12][12][(1<<13)+10] ;

int main()
{
    int i , n ,m , j, k ,case1=0;
    int len , T ,x ,y ,sta ;
    cin >>T ;
    while(T--)
    {
        scanf("%d%d",&n,&m) ;
        for( i = 1 ; i <= n ;i++)
           for( j = 1 ; j <= m ;j++)
             scanf("%d",&mat[i][j]);
        memset(dp,0,sizeof(dp)) ;
        len=(1<<(m+1));
        dp[0][m][0]=1;
        for( i = 1 ; i <= n ;i++)
        {
            for( j = 0 ; j < len ;j++)
                dp[i][0][(j<<1)]=dp[i-1][m][j] ;
            for( k = 1 ; k <= m ;k++)
            {
                for( sta = 0 ; sta < len ;sta++)
                {
                    y = (1<<k) ;
                    x = (1<<(k-1)) ;
                    if(mat[i][k])
                    {
                        if((sta&x) && (sta&y))
                            dp[i][k][sta]=dp[i][k-1][sta^x^y] ;
                        else if((sta&x)==0 && 0== (sta&y))
                            dp[i][k][sta]=dp[i][k-1][sta^x^y] ;
                        else dp[i][k][sta]=dp[i][k-1][sta^x^y]+dp[i][k-1][sta] ;
                    }
                    else
                    {
                        if((sta&x)==0&&(sta&y)==0)
                            dp[i][k][sta]=dp[i][k-1][sta] ;
                        else dp[i][k][sta]=0;
                    }
                }
            }
        }
        printf("Case %d: ",++case1);
         printf("There are %I64d ways to eat the trees.\n",dp[n][m][0]);
    }
    return 0 ;
}

时间: 2024-08-06 03:46:27

插头DP学习的相关文章

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

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

hdu1693插头dp(多回路)

题意:在n*m的矩阵中,有些格子有树,没有树的格子不能到达,找一条或多条回路,吃全然部的树,求有多少中方法. 这题是插头dp,刚刚学习,不是非常熟悉,研究了好几天才明确插头dp的方法,他们老是讲一些什么轮廓线啊,插头啊什么的,刚開始全然不知道这些事干什么的,看完cdq的大作后也是一头的雾水,看代码,绘图,一步一步的推,最终明确了,那个是为什么,这里讲一讲. 轮廓线表示的是当前插头的状态,这题中状态中1表示有插头,0表示无插头,假设是横线的话就是上面的格子与以下的格子相连的状态,这题中显然一个格子

hdu 1693 Eat the Trees (插头dp入门)

Eat the Trees Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2507    Accepted Submission(s): 1225 Problem Description Most of us know that in the game called DotA(Defense of the Ancient), Pudg

无聊的 邮递员 插头dp

邮递员想知道,如果他每天都用不同路线走过10×20个点阵邮筒,他必须活过多少个世纪才能走遍所有方案? 7:00 改完T1,开始肝插头dp 7:10 放弃,颓博客 7:20 学习插头dp 7:21 放弃,抄代码 8:30 抄完结构体,负罪感强烈,自打分类讨论 从此 0分:打了一个分类讨论就把它复制粘贴到另一个类似的分类讨论里,改都不改... 0分:为了避免hash出负,行间转移时将状态&full  结果不小心把最高位那个有用的信息舍弃了... 0分:link函数出锅,根本没考虑全所有的情况,也就是

插头DP总结

从跳楼到蒙B...插头DP,电源插头DP,工业插头DP,插座DP...额我们先将一些技能的,比如说hash表,这年头连hash表都不会打,简直就是yasi.hash使用一个表头加链表的结构实现数组的使用.比如说要统计值域到1e9的一列数出现次数,显然我们就把一列数压一下,比如模一个质数同余的放在一起,然后把第一个挂在表头,其它的用前向星或者链表连一下,存一下信息,一般情况还是很快的.查询就是从表头一个一个比.大概是这样,重载中括号,查询+插入,使用和数组相同,学长的代码就是帅的一批 struct

插头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> #

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)