插头DP入门

Posted toot-wjh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了插头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;

 

以上是关于插头DP入门的主要内容,如果未能解决你的问题,请参考以下文章

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

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

猴子课堂:插头DP(基于连通性状态压缩的动态规划问题)(让你从入门到绝望)

[BZOJ]|[Ura] Formula 1-----插头DP入门

插头dp模板

插头dp