插头DP入门
Posted toot-wjh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了插头DP入门相关的知识,希望对你有一定的参考价值。
终于来补插头DP的坑了,咕了好久,主要是因为博猪代码实现能力太弱,而网上的大神们都只讲分类讨论。。。
只放代码了:
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
#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(基于连通性状态压缩的动态规划问题)(让你从入门到绝望)