插头DP总结
特征:
一般为网格图
求解联通性问题或给出特定形状
可状压的数据范围
插头可定义,能够归纳轮廓线以上的情况且推出所以以下的情况,统计类要不充不漏(统计答案时判掉也行)
状态数不多
实现:
Hash表压缩状态
强烈建议使用函数,嵌套调用节约码长,不容易出错。
1 int sc[5]={0,1,-1}; 2 struct Hash_Table{ 3 #define mod 131313 4 int fir[mod+5],nxt[mod+5],key[mod+5],o; 5 ll val[mod+5]; 6 void clear(){o=0;memset(fir,0,sizeof fir);} 7 ll& operator[](int x){ 8 int pos=x%mod; 9 for(reg int i=fir[pos];i;i=nxt[i]) 10 if(key[i]==x)return val[i]; 11 key[++o]=x; 12 nxt[o]=fir[pos]; 13 fir[pos]=o; 14 return val[o]=0; 15 } 16 }f[2]; 17 int find(int sta,int pos) 18 { 19 return sta>>(pos-1<<1)&3; 20 } 21 int ch(int sta,int pos,int val) 22 { 23 pos=(pos-1)<<1; 24 sta|=3<<pos; 25 sta^=3<<pos; 26 sta|=val<<pos; 27 return sta; 28 } 29 int link(int sta,int pos) 30 { 31 int dt=sc[find(sta,pos)],cnt=0; 32 for(reg int i=pos;i&&i<=m+1;i+=dt){ 33 cnt+=sc[find(sta,i)]; 34 if(cnt==0)return i; 35 } 36 return -1; 37 }
find(sta,pos) 返回sta中pos位的插头类型
ch(sta,pos,val) 更改sta中pos位的插头类型为val
link(sta,pos) 返回sta中pos位插头对应的另一端位置
调错(因为真的很难调qwq):
输出(i,j),状态,插头类型
模出样例形状,看在对应的位置有没有正确的状态。
link()函数返回-1可以便于反映bug。
RE一般是hash表开小或状态出负(以上)
注意:插头是已经发生的状态,但对于不好在当前判定合法性的状态,可以先出插头累加当前格而不算转移格的贡献,放在之后判定。
例题:以下重点在于定义插头
《Ural 1519 Formula 1》:一个 m * n 的棋盘,有的格子存在障碍,求经过所有非障碍格子的哈密顿回路个数。
采用括号匹配压状态,定义1为左括号,2为右括号。
《CITY》:插头同理,与上题不同只在于转移条件。
《地板》:地板只能转向一次,于是定义插头1为未转向,2为已转向。
《标识设计》:三个L没有本质区别,体现在没有顺序、不可合并,所以定义单插头表示L的走向。但必须保证数量,所以记录状态中已开始和已结束的插头数量(<=3)。转移完统计开始和结束都为3的答案即可。可以不记录已结束数,最后判下没有插头就行。
《神奇游乐园》:不同在于:
1.在任何位置都可以闭合也可以无插头。加上对应的转移。
2.最优化问题。 改为取max
《ParkII》要求的是路径,所以只有括号插头不再适用
定义1 2为括号插头,3为独立插头
多出来的转移:
1.括号插头和独立插头合并,改另一端为独立插头
2.独立和独立插头合并,若无其他插头则统计答案,否则是不合法状态舍去。
3.p1=1&p2=2 括号合并,不合法
4.括号插头固定一端,另一端变独立插头