插头DP入门

  终于来补插头DP的坑了,咕了好久,主要是因为博猪代码实现能力太弱,而网上的大神们都只讲分类讨论。。。

  只放代码了:

  zzh学长:

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define ll long long
  4 #define A 1100000
  5 #define mod 299989
  6 #define P 8
  7 #define N 100000000
  8 ll n,m;
  9 inline ll find(ll state,ll id){
 10     return (state>>((id-1)<<1))&3;
 11 }//看当前插头究竟是什么插头
 12 //因为是四进制每两位代表一个状态
 13 struct bignum
 14 {
 15     ll n[10],l;
 16     bignum(){l=1,memset(n,0,sizeof(n));}
 17     void clear(){while(l>1&&!n[l-1]) l--;}
 18     void print(){
 19         printf("%lld",n[l-1]);
 20         for(ll i=l-2;i>=0;i--)
 21             printf("%0*lld",P,n[i]);
 22         printf("\n");
 23     }
 24     bignum operator +(bignum x)const{
 25         bignum t=*this;
 26         if(t.l<x.l) t.l=x.l;
 27         t.l++;
 28         for(ll i=0;i<t.l;i++){
 29             t.n[i]+=x.n[i];
 30             if(t.n[i]>=N){
 31                 t.n[i+1]+=t.n[i]/N;
 32                 t.n[i]%=N;
 33             }
 34         }
 35         t.clear();
 36         return t;
 37     }
 38     bignum operator =(ll x){
 39         l=0;
 40         while(x){
 41             n[l++]=x%N;
 42             x/=N;
 43         }
 44         return *this;
 45     }
 46     inline void operator +=(bignum b){*this=*this+b;}
 47 }Ans;
 48 struct hash_map
 49 {
 50     bignum val[mod];
 51     ll key[A],hash[mod],size;
 52     inline void init(){
 53         memset(val,0,sizeof(val));
 54         memset(key,-1,sizeof(key));size=0;
 55         memset(hash,0,sizeof(hash));
 56     }
 57     inline void newhash(ll id,ll v){hash[id]=++size;key[size]=v;}
 58     bignum &operator [] (const ll &state){
 59         for(ll i=state%mod;;i=(i+1==mod)?0:i+1)
 60         {
 61             if(!hash[i]) newhash(i,state);
 62             if(key[hash[i]]==state) return val[hash[i]];
 63         }
 64     }
 65 }f[2];
 66 inline void Set(ll &state,ll bit,ll val){
 67     bit=(bit-1)<<1;
 68     state|=3<<bit;
 69     state^=3<<bit;
 70     //把state高位先赋成0再把它赋成别的
 71     state|=val<<bit;
 72     //state表示状态
 73     //因为插头的编号为0--m所以bit要-1
 74     //因为是四进制,所以3<<
 75     //全都是4进制
 76     //2<<bit
 77     //1<<bit
 78     //貌似还能理解
 79     //每两位代表一个具体状态
 80 }
 81 ll link(ll state,ll pos){
 82     //找到对应的插头(用括号匹配的方式)然后
 83     ll cnt=0,delta=(find(state,pos)==1)?1:-1;
 84     //如果是左括号应该向右寻找右括号
 85     //如果是右括号应该向左寻找左括号
 86     for(ll i=pos;i&&i<=m+1;i+=delta)//一共m+1个插头
 87     {
 88         ll plug=find(state,i);
 89         if(plug==1)
 90             cnt++;//左括号数量++
 91         else if(plug==2)
 92             cnt--;//右括号数量++
 93         if(cnt==0)//当左括号数量与右括号数量相等时找到匹配
 94              return i;//找到了与当前插头对应的插头
 95     }
 96     return -1;
 97     //当前状态是非法的找不到与之对应的插头
 98 }
 99 inline void education(ll x,ll y){
100     ll now=((x-1)*m+y)&1,last=now^1,tot=f[last].size;
101     f[now].init();
102     for(ll i=1;i<=tot;i++){
103 //        printf("i=%lld\n",i);
104         ll state=f[last].key[i];//key状态
105         bignum Val=f[last].val[i];//取出上一次权值(方案数)
106         ll plug1=find(state,y),plug2=find(state,y+1);
107         //0--m编号,寻找轮廓线上编号y-1,y对应的插头
108         //至于为什么是y y+1,因为在上面函数里进行了减1
109         //编号为y-1是左右插头,y代表上下插头
110         if(link(state,y)==-1||link(state,y+1)==-1)
111             continue;
112         //当前括号无法找到匹配无解
113         if(!plug1&&!plug2){
114             if(x!=n&&y!=m){
115         //如果没有插头,直接拽过来两个插头相连(此题保证必须连通)
116                 Set(state,y,1);
117                 //在轮廓线上位置为y-1添加一个左括号
118                 Set(state,y+1,2);
119                 //y位置添加一个右括号
120                 f[now][state]+=Val;
121             }
122         }
123         else if(plug1&&!plug2){
124         //拥有左右插头没有上下插头
125         //两种转移方式,转弯向下走
126         //这样插头状态不变
127             if(x!=n)
128                 f[now][state]+=Val;
129         //向右连接一个插头
130         //向右推进要发生改变
131             if(y!=m){
132                 Set(state,y,0);
133                 Set(state,y+1,plug1);
134                 f[now][state]+=Val;
135             }
136         }
137         else if(!plug1&&plug2){
138         //拥有上下插头而没有左右插头
139         //两种转移方式,向右转移
140         //这样插头状态不变
141             if(y!=m)
142                 f[now][state]+=Val;
143         //向右连接一个插头
144             if(x!=n){
145                 Set(state,y,plug2);
146                 Set(state,y+1,0);
147                 //plug2是左右插头让上下方向转弯
148                 f[now][state]+=Val;
149             }
150         }
151         else if(plug1==1&&plug2==1){
152             //两个左括号插头相连接,然后让最靠左的右括号插头变成左括号插头
153             Set(state,link(state,y+1),1);
154             Set(state,y,0);
155             Set(state,y+1,0);
156             f[now][state]+=Val;
157         }
158         else if(plug1==1&&plug2==2){
159             //右插头是左括号插头,上插头是右括号插头,连在一起
160             //构成回路
161             if(x==n&&y==m)
162                 Ans+=Val;
163         }
164         else if(plug1==2&&plug2==1){
165             //无脑合并
166             Set(state,y,0);
167             Set(state,y+1,0);
168             f[now][state]+=Val;
169         }
170         else if(plug1==2&&plug2==2){
171             //当你有左右插头右括号插头,上下插头为右插头
172             //合并为1,将最靠右左插头变为右插头
173             Set(state,link(state,y),2);
174             Set(state,y,0);
175             Set(state,y+1,0);
176             f[now][state]+=Val;
177         }
178     }
179 }
180 int main(){
181     scanf("%lld%lld",&n,&m);
182     if(n==1||m==1){printf("1\n");return 0;}
183     if(m>n) swap(n,m);
184     f[0].init();f[0][0]=1;
185     for(ll i=1;i<=n;i++)
186     {
187         for(ll j=1;j<=m;j++)
188             education(i,j);
189         if(i!=n){
190             ll now=(i*m)&1,tot=f[now].size;
191             for(ll j=1;j<=tot;j++)
192                 f[now].key[j]<<=2;
193         }
194     }
195     Ans+=Ans;
196     Ans.print();
197 }

zzn大神解读版

  另一位大神:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int LIM=300005,Has=299989;
int n,m,e1,e2,v,u;LL ans;
int mp[15][15],bin[15],t[2];LL w[2][LIM];
int head[300005],to[2][LIM],Next[LIM];//t:状态总数,w:该状态的方案总数,to:各种状态
void ins(int zt,LL num) {//卓越的哈希技术
    int tmp=zt%Has+1;
    for(int i=head[tmp];i;i=Next[i])
        if(to[u][i]==zt) {w[u][i]+=num;return;}
    Next[++t[u]]=head[tmp],head[tmp]=t[u];
    to[u][t[u]]=zt,w[u][t[u]]=num;
}
void work() {
    t[u]=1,w[u][1]=1,to[u][1]=0;
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=t[u];++j) to[u][j]<<=2;//切换行了
        for(int j=1;j<=m;++j) {
            v=u,u^=1;
            memset(head,0,sizeof(head)),t[u]=0;
            for(int k=1;k<=t[v];++k) {
                int zt=to[v][k],b1=(zt>>(j*2-2))%4,b2=(zt>>(j*2))%4;//提取关键格子上的两段轮廓线状态
                LL num=w[v][k];
                if(!mp[i][j]) {if(!b1&&!b2) ins(zt,num);}
                else if(!b1&&!b2) {if(mp[i+1][j]&&mp[i][j+1]) ins(zt+bin[j-1]+2*bin[j],num);}
                else if(!b1&&b2) {
                    if(mp[i][j+1]) ins(zt,num);
                    if(mp[i+1][j]) ins(zt-bin[j]*b2+bin[j-1]*b2,num);
                }
                else if(b1&&!b2) {
                    if(mp[i][j+1]) ins(zt-bin[j-1]*b1+bin[j]*b1,num);
                    if(mp[i+1][j]) ins(zt,num);
                }
                else if(b1==1&&b2==1) {
                    int kl=1;
                    for(int ttt=j+1;ttt<=m;++ttt) {
                        if((zt>>(ttt*2))%4==1) ++kl;
                        if((zt>>(ttt*2))%4==2) --kl;
                        if(!kl) {ins(zt-bin[j]-bin[j-1]-bin[ttt],num);break;}
                    }
                }
                else if(b1==2&&b2==2) {
                    int kl=1;
                    for(int ttt=j-2;ttt>=0;--ttt) {
                        if((zt>>(ttt*2))%4==1) --kl;
                        if((zt>>(ttt*2))%4==2) ++kl;
                        if(!kl) {ins(zt+bin[ttt]-2*bin[j]-2*bin[j-1],num);break;}
                    }
                }
                else if(b1==2&&b2==1) ins(zt-2*bin[j-1]-bin[j],num);
                else if(i==e1&&j==e2) ans+=num;
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j) {
        char ch=getchar();
        while(ch^‘*‘&&ch^‘.‘) ch=getchar();
        if(ch==‘.‘) mp[i][j]=1,e1=i,e2=j;
    }
    bin[0]=1;for(int i=1;i<=12;++i) bin[i]=bin[i-1]<<2;
    work(),printf("%lld\n",ans);
    return 0;
}

原文地址:https://www.cnblogs.com/toot-wjh/p/11298150.html

时间: 2024-10-07 05:21:39

插头DP入门的相关文章

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入门

基于联通性的状态压缩动态规划是一类很典型的状态压缩动态规划问题,因为其压缩的本质并不像是普通的状态压缩动态规划那样用0或者1来表示未使用.使用两种状态,而是使用数字来表示类似插头的状态,因此,它又被称作插头DP. 插头DP本质上是一类状态压缩DP,因此,依然避免不了其指数级别的算法复杂度,即便如此,它依然要比普通的搜索算法快很多. [例]Postal Vans(USACO training 6.1.1) 有一个4*n的矩阵,从左上角出发,每次可以向四个方向走一步,求经过每个格子恰好一次,再回到起

[URAL1519] Formula 1 [插头dp入门]

题面: 传送门 思路: 先理解一下题意:实际上就是要你求这个棋盘中的哈密顿回路个数,障碍不能走 看到这个数据范围,还有回路处理,就想到使用插头dp来做了 观察一下发现,这道题因为都是回路,所以联通块上方的插头一定两两配对,可以使用括号序列代替最小表示法 分情况讨论一下 情况一:当前格子上方和左方都没有插头 这种时候可以继续,也可以给当前格子加一个下插头一个右插头,相当于一个新的联通分量 情况二:上方有一个下插头,左边没有 这时有两个决策:可以向右转,也可以继续向下,操作就是分别给这个格子一个右插

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

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

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)

插头DP专题

建议入门的人先看cd琦的<基于连通性状态压缩的动态规划问题>.事半功倍. 插头DP其实是比较久以前听说的一个东西,当初是水了几道水题,最近打算温习一下,顺便看下能否入门之类. 插头DP建议先理解“插头”的概念.然后会HASH表(这个其实是很基础的东西,应该都会的).然后就是DP. 以及特殊题目的特殊处理. 好像一般是求N,M<=12的网格图的某种回路数或某种通路数的方案数. 大体上每个题说几句特殊处理,有问题请纠正....题目的顺序基本上难度递增 另外代码我都是用括号匹配的.因为感觉连通

插头dp模板

(入门):https://wenku.baidu.com/view/4fe4ac659b6648d7c1c74633.html (进阶)http://www.cnblogs.com/kuangbin/archive/2012/10/02/2710343.html 插头DP其实就是每格进行状态转移. 最小表示法太强了 保证数字≤状态长度,方便压成一个状态表示. 特点:数据小.非障碍格子连通. struct HASHMAP{ int head[HASH],next[STATE],state[STAT

HDU 2084 数塔(简单DP入门)

数塔 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 41852    Accepted Submission(s): 24820 Problem Description 在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

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