tip:
插头 dp 主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有很多种,并且一般数据规模较小。由于棋盘有很特殊的结构,使得它可以与“连通性”有很强的联系,因此插头 dp 最常见的应用要数在棋盘模型上的应用了。
实战:
T1:Ural 1519 Formula 1
题干:
一个 m * n 的棋盘,有的格子存在障碍,求经过所有非障碍格子的哈密顿回路个数
题解:
所谓‘哈密顿回路’,就是不相交的欧拉回路。
一道插头 dp 的裸题。
这道题 Hash 表的应用十分关键。 Hash 表的查询与添加都比较出色,使得它在时间与空间上都十分优秀,只不过需要处理好模数(就情况数来决定)。粘一下代码:
(其实它就跟前向星十分相似,都有nxt、self、first、val(其实就是链表))
再看一下题,其实就是考虑一下每个块是否有左插头(p1)与上插头(p2)。每个插头指向的块又可以向右、向下制造插头。(注意一下转移的时候是只看横向的状态,根据插头的种类向 Hash 表中放状态)
(p1==1 时表示它是左括号,p1==2 时表示它是右括号)
实现过程:
1、先将上一层的状态 <<2 (给这次转移留好位置,又因为这道题就只有 3 种状态(01 10 11),就用四进制了)
2、
Code:
1 #include<cstdio>
2 #include<cstring>
3 #include<cstdlib>
4 #define int long long
5 #define $ 40000000
6 #define hash 299987
7 using namespace std;
8 int m,n,a[15][15],nxt[$],first[300000];
9 int h[16],ans,self[2][$],now,last,tot[2],val[2][$],s,t;
10 inline void add(int x,int add){
11 int tmp=x%hash+1;
12 for(register int i=first[tmp];i;i=nxt[i])
13 if(self[now][i]==x){ val[now][i]+=add; return; }
14 nxt[++tot[now]]=first[tmp];
15 first[tmp]=tot[now];
16 self[now][tot[now]]=x;
17 val[now][tot[now]]=add;
18 }
19 signed main(){
20 scanf("%lld%lld",&n,&m);
21 for(register int i=1;i<=n;++i)
22 for(register int j=1;j<=m;++j){
23 char ch=getchar();
24 while(ch!='*'&&ch!='.') ch=getchar();
25 if(ch=='.') a[i][j]=1, s=i, t=j;
26 }
27 h[0]=1;
28 for(register int i=1;i<=15;++i) h[i]=h[i-1]<<2;
29 tot[now]=1, val[now][1]=1, self[now][1]=0;
30 for(register int i=1;i<=n;++i){
31 for(register int j=1;j<=tot[now];++j) self[now][j]<<=2;
32 for(register int j=1;j<=m;++j){
33 memset(first,0,sizeof(first));
34 last=now, now^=1; tot[now]=0;
35 for(register int k=1;k<=tot[last];++k){
36 int tmp=self[last][k], w=val[last][k];
37 int p1=(tmp>>((j-1)*2))%4, p2=(tmp>>(j*2))%4;
38 if(!a[i][j]){ if(!p1&&!p2) add(tmp,w); }
39 else if(!p1&&!p2){
40 if(a[i+1][j]&&a[i][j+1]) add(tmp+h[j-1]+h[j]*2,w);
41 }
42 else if(!p1&&p2){
43 if(a[i][j+1]) add(tmp,w);
44 if(a[i+1][j]) add(tmp-h[j]*p2+h[j-1]*p2,w);
45 }
46 else if(p1&&!p2){
47 if(a[i+1][j]) add(tmp,w);
48 if(a[i][j+1]) add(tmp-h[j-1]*p1+h[j]*p1,w);
49 }
50 else if(p1==1&&p2==1){
51 int tip=1;
52 for(register int l=j+1;l<=m;++l){
53 if((tmp>>(l*2))%4==1) ++tip;
54 if((tmp>>(l*2))%4==2) --tip;
55 if(tip==0){
56 add(tmp-h[j]-h[j-1]-h[l],w); break;
57 }
58 }
59 }
60 else if(p1==2&&p2==2){
61 int tip=1;
62 for(register int l=j-2;l>=0;--l){
63 if((tmp>>(l*2))%4==1) --tip;
64 if((tmp>>(l*2))%4==2) ++tip;
65 if(tip==0){
66 add(tmp-h[j]*2-h[j-1]*2+h[l],w); break;
67 }
68 }
69 }
70 else if(p1==2&&p2==1) add(tmp-h[j-1]*2-h[j],w);
71 else if(p1==1&&p2==2&&i==s&&j==t) ans+=w;
72 }
73 }
74 }
75 printf("%lld
",ans);
76 }
T2:邮递员
题干:
Smith 在 P 市的邮政局工作,他每天的工作是从邮局出发,到自己所管辖的所有邮筒取信件,然后带回邮局。他所管辖的邮筒非常巧地排成了一个 m * n 的点阵(点阵中的间距都是相等的)。左上角的邮筒恰好在邮局的门口。 Smith 是一个非常标新立异的人,他希望每天都能走不同的路线,但是同时,他又不希望路线的长度增加,他想知道他有多少条不同的路线可走。
你的程序需要根据给定的输入,给出符合题意的输出:输入包括点阵的 m 和 n 的值;你需要根据给出的输入,计算出 Smith 可选的不同路线的总条数;
题解:
Code:
1 #include<cstdio>
2 #include<cstring>
3 #include<cstdlib>
4 #define $ 500100
5 #define hash 299987
6 using namespace std;
7 int n,m,s,t,now,last,self[2][$],nxt[$],tot[2],h[15],first[hash+10],a[21][11];
8 struct tree{
9 int a[22];
10 tree(){ memset(a,0,sizeof(a)); }
11 friend tree operator + (const tree &a,const tree &b){
12 tree c=tree();
13 int yu=0,adda=0,addb=0;
14 for(register int i=1;i<=a.a[0]||i<=b.a[0]||yu>0;++i,adda=0,addb=0){
15 if(i<=a.a[0]) adda=a.a[i];
16 if(i<=b.a[0]) addb=b.a[i];
17 c.a[i]=adda+addb+yu;
18 yu=c.a[i]/10000; c.a[i]%=10000;
19 c.a[0]=i;
20 }
21 return c;
22 }
23 inline void print(){
24 printf("%d",a[a[0]]);
25 for(register int i=a[0]-1;i>=1;--i) printf("%04d",a[i]);
26 }
27 }ans,val[2][$],w;
28 inline void swap(int &x,int &y){ int t=y; y=x; x=t; }
29 inline int min(int x,int y){ return x<y?x:y; }
30 inline void add(const int &x,const tree &add){
31 int tmp=x%hash+1;
32 for(register int i=first[tmp];i;i=nxt[i])
33 if(self[now][i]==x){ val[now][i]=val[now][i]+add; return ; }
34 nxt[++tot[now]]=first[tmp];
35 first[tmp]=tot[now];
36 val[now][tot[now]]=add;
37 self[now][tot[now]]=x;
38 }
39 signed main(){
40 h[0]=1;
41 for(register int i=1;i<=12;++i) h[i]=h[i-1]<<2;
42 scanf("%d%d",&n,&m);
43 if(n<m) swap(n,m);
44 if(min(m,n)==1){ puts("1"); return 0; }
45 for(register int i=1;i<=n;++i)
46 for(register int j=1;j<=m;++j) a[i][j]=1;
47 w.a[0]=w.a[1]=1; add(0,w);
48 for(register int i=1;i<=n;++i){
49 for(register int j=1;j<=tot[now];++j) self[now][j]<<=2;
50 for(register int j=1;j<=m;++j){
51 memset(first,0,sizeof(first));
52 last=now, now^=1, tot[now]=0;
53 for(register int k=1;k<=tot[last];++k){
54 int tmp=self[last][k], p1=(tmp>>((j-1)*2))%4, p2=(tmp>>(j*2))%4;
55 w=val[last][k];
56 if(!a[i][j]){ if(!p1&&!p2) add(tmp,w); }
57 else if(!p1&&!p2){
58 if(a[i+1][j]&&a[i][j+1]) add(tmp+h[j-1]+h[j]*2,w);
59 }
60 else if(!p1&&p2){
61 if(a[i][j+1]) add(tmp,w);
62 if(a[i+1][j]) add(tmp-p2*h[j]+p2*h[j-1],w);
63 }
64 else if(p1&&!p2){
65 if(a[i+1][j]) add(tmp,w);
66 if(a[i][j+1]) add(tmp-p1*h[j-1]+p1*h[j],w);
67 }
68 else if(p1==1&&p2==1){
69 int tip=1;
70 for(register int l=j+1;l<=m;++l){
71 if((tmp>>(l*2))%4==1) ++tip;
72 if((tmp>>(l*2))%4==2) --tip;
73 if(tip==0){ add(tmp-h[j-1]*p1-h[j]*p2-h[l],w); break; }
74 }
75 }
76 else if(p1==2&&p2==2){
77 int tip=1;
78 for(register int l=j-2;l>=0;--l){
79 if((tmp>>(l*2))%4==1) --tip;
80 if((tmp>>(l*2))%4==2) ++tip;
81 if(tip==0){ add(tmp-p1*h[j-1]-p1*h[j]+h[l],w); break; }
82 }
83 }
84 else if(p1==2&&p2==1) add(tmp-h[j-1]*p1-h[j]*p2,w);
85 else if(p1==1&&p2==2&&i==n&&j==m) ans=ans+w;
86 }
87 }
88 }
89 ans=ans+ans; ans.print();
90 }
T3:CITY
题干:
小明和小华要参加 NOI,踏上了去 X 市的火车。
小明望着窗外的田野,大楼,工厂缓缓后退,在思考着什么。
这时,对面的小华拿出手机对着他说:“看!我们在这个位置!”
小明望着手机上显示的地图,城市被接到分割成各个方块,而自己所在的点在慢慢移动。
他突然意识到自己甚至还没游历过这个自己所在的小城市,学校和家貌以及之间来回的道路似乎成了这个小城的唯一印象。
若我把它们全部走一圈,可能要仔细计划下吧……不,那么多方案,其实我应该早能做到了吧……小明在心里对自己说。
第一行有两个数 N , M 表示地图被分割成 N * M 个块,接下来有 N 行,每行有 M 个字符。
输入格式:
. 表示这个块可以通过
- 表示这个块只可以左右通过
| 表示这个块只可以上下通过
# 表示这个块不能通过
(从每个块只能走到其上下左右相邻的四个块)
输出格式:一个数,表示小明把所以可以通过的块都经过且只经过一次并回到原地的方案数。
题解:
Code:
1 #include<cstdio>
2 #include<cstring>
3 #include<cstdlib>
4 #define $ 2000010
5 #define hash 299987
6 #define int long long
7 using namespace std;
8 int n,m,first[hash+10],nxt[$],val[2][$],self[2][$];
9 int a[14][14],h[14],now,last,s,t,tot[2],ans;
10 inline void add(int x,int add){
11 int tmp=x%hash+1;
12 for(register int i=first[tmp];i;i=nxt[i])
13 if(self[now][i]==x){ val[now][i]+=add; return; }
14 nxt[++tot[now]]=first[tmp];
15 first[tmp]=tot[now];
16 val[now][tot[now]]=add;
17 self[now][tot[now]]=x;
18 }
19 signed main(){
20 h[0]=1;
21 for(register int i=1;i<=13;++i) h[i]=h[i-1]<<2;
22 scanf("%lld%lld",&n,&m);
23 for(register int i=1;i<=n;++i)
24 for(register int j=1;j<=m;++j){
25 char ch=getchar();
26 while(ch!='.'&&ch!='|'&&ch!='-'&&ch!='#') ch=getchar();
27 if(ch=='.') a[i][j]=1, s=i, t=j;
28 if(ch=='-') a[i][j]=2;
29 if(ch=='|') a[i][j]=3;
30 }
31 tot[now]=1, self[now][tot[now]]=0, val[now][tot[now]]=1;
32 for(register int i=1;i<=n;++i){
33 for(register int j=1;j<=tot[now];++j) self[now][j]<<=2;
34 for(register int j=1;j<=m;++j){
35 memset(first,0,sizeof(first));
36 last=now, now^=1, tot[now]=0;
37 for(register int k=1;k<=tot[last];++k){
38 int tmp=self[last][k], p1=(tmp>>((j-1)*2))&3, p2=(tmp>>(j*2))&3;
39 int w=val[last][k];
40 if(a[i][j]==0){
41 if(!p1&&!p2) add(tmp,w);
42 }
43 else if(a[i][j]==3){
44 if(!p1&&p2&&(a[i+1][j]==1||a[i+1][j]==3)) add(tmp-p2*h[j]+p2*h[j-1],w);
45 }
46 else if(a[i][j]==2){
47 if(p1&&!p2&&(a[i][j+1]==1||a[i][j+1]==2)) add(tmp-p1*h[j-1]+p1*h[j],w);
48 }
49 else if(!p1&&!p2){
50 if((a[i+1][j]==1||a[i+1][j]==3)&&(a[i][j+1]==1||a[i][j+1]==2))
51 add(tmp+h[j-1]+h[j]*2,w);
52 }
53 else if(!p1&&p2){
54 if(a[i][j+1]==1||a[i][j+1]==2) add(tmp,w);
55 if(a[i+1][j]==1||a[i+1][j]==3) add(tmp-p2*h[j]+p2*h[j-1],w);
56 }
57 else if(p1&&!p2){
58 if(a[i+1][j]==1||a[i+1][j]==3) add(tmp,w);
59 if(a[i][j+1]==1||a[i][j+1]==2) add(tmp-p1*h[j-1]+p1*h[j],w);
60 }
61 else if(p1==1&&p2==1){
62 int tip=1;
63 for(register int l=j+1;l<=m;++l){
64 if(((tmp>>(l*2))&3)==1) ++tip;
65 if(((tmp>>(l*2))&3)==2) --tip;
66 if(tip==0){ add(tmp-p1*h[j-1]-p2*h[j]-h[l],w); break; }
67 }
68 }
69 else if(p1==2&&p2==2){
70 int tip=1;
71 for(register int l=j-2;l>=0;--l){
72 if(((tmp>>(l*2))&3)==1) --tip;
73 if(((tmp>>(l*2))&3)==2) ++tip;
74 if(tip==0){ add(tmp-p1*h[j-1]-p2*h[j]+h[l],w); break; }
75 }
76 }
77 else if(p1==2&&p2==1) add(tmp-p1*h[j-1]-p2*h[j],w);
78 else if(p1==1&&p2==2&&i==s&&j==t) ans+=w;
79 }
80 }
81 }
82 printf("%lld",ans);
83 }
T1:
题干:
题解:
Code:
T1:
题干:
题解:
Code: