Hnoi2007-Day1有一道题目 Park:给你一个 m * n 的矩阵,每一个矩阵内有个
权值V(i,j) (可能为负数),要求找一条回路,使得每一个点最多通过一次,而且通过
的点权值之和最大,想必你们印象深入吧.
无聊的小 C 同窗把这个问题稍微改了一下:要求找一条路径,使得每一个点
最多通过一次,而且点权值之和最大,若是你跟小 C 同样无聊,就麻烦作一下
这个题目吧.
转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7326874.htmlhtml
最近搞了一下插头DP的基础知识……这真的是一种很锻炼人的题型……node
每一道题的状态都不同,而且有很多的分类讨论,让插头DP十分锻炼思惟的全面性和严谨性。数组
下面咱们一块儿来学习插头DP的内容吧!app
插头DP主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有不少种,而且通常数据规模较小。ide
因为棋盘有很特殊的结构,使得它能够与“连通性”有很强的联系,所以插头DP最多见的应用要数在棋盘模型上的应用了。函数
下面咱们给出一道很简单的例题,而且由这道简单的例题构建出插头DP的基本解题思路,在状态确立,状态转移以及程序实现几个方面进行一一介绍.post
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)学习
首先,咱们要了解插头DP中最重要的关键词:“插头”优化
---插头ui
在插头DP中,插头表示一种联通的状态,以棋盘为例,一个格子有一个向某方向的插头,就意味着这个格子在这个方向能够与外面相连(与插头那边的格子联通)。
值得注意的一点是,插头不是表示将要去某处的虚拟状态,而是表示已经到达某处的现实状态。
也就是说,若是有一个插头指向某个格子,那么这个格子已经和插头来源联通了,咱们接下来要考虑的是从这个插头往哪里走。
这是很重要的一点理解,下面讨论的状态转移都是在此基础上展开的,请务必注意。
咱们已经有了插头,天然要利用插头来状态转移。通常来讲,咱们从上往下,从左往右逐行逐格递推。
---逐格递推
咱们考虑第i行的某一个格子:走向它的方案,可能由上一行的下插头转移而来,也多是本行的右插头转移而来。
所以咱们须要记录这些地方有没有插头,也就是利用状压的思想。咱们记录的这个“有没有插头”的东西,就被咱们称为轮廓线。字面意思,轮廓线就是记录了棋盘这一行与上一行交界的轮廓中插头的状况。轮廓线上方是已经决策完的格子,下方是未决策的。显然,对于本题的轮廓线,与它直接相连的格子有m个,插头有m+1个,我我的的习惯是给插头编号0~m。
·上图就是轮廓线的一种可能状况。
因为数据范围比较小,轮廓线的插头状态咱们通常能够利用X进制压位来表示。
对于本题来讲,题目的限制条件比较少,能够走多个回路,而不是像某些题同样只能走一个回路(走一个回路的时候要维护插头间的连通性,咱们下文再讨论),所以咱们直接记录,只要用二进制来表示某一位置有没有插头便可:设0表示没有插头,1表示有插头。(大概这是最简单的一种插头类型了......)
---行间转移
咱们先考虑两行之间的转移:显然,第i行的下插头决定了第i+1行的格子有没有上插头,所以咱们应该把这个信息传递到下一行。
在转移的时候,当前行插头0到插头m-1可能会给下一行带来贡献,而第m个插头必定为0(结合定义,想一下为何)。
容易发现,当前行的0~m-1号插头会变成下一行初始的1~m号插头,所以咱们能够直接利用位运算进行转移。
对于本题,只须要将上一行的某个状态左移一位(<<1,即*2)便可
行间的转移仍是比较简单的,具体代码实现的话,下面是一种能够参考的方式
(这是我刚学插头DP时候用的一种比较蠢的打法,使用状态数组f[i][j][k]表示决策到第i行第j列,插头状态为k的方案数,后面使用Hash表的时候咱们还有其余方式)
1 if(i<n)//bin[i]表示2的i次方 2 for(int j=0;j<bin[m];j++) 3 f[i+1][0][j<<1]=f[i][m][j];
下面咱们考虑具体的逐格转移,这也是插头DP的核心模块所在。
对于本题来讲,当咱们决策到某个格子(x,y)时,假如它不是障碍格子,可能会出现以下三种状况:
状况1,这个格子没有上插头,也没有左插头,那么因为咱们要遍历整张图,因此咱们要新建插头,把这个格子与其余格子连起来,相应的,咱们要把原来轮廓线对应位置的插头改成1.
状况2,这个位置有上插头,也有左插头。因为咱们不要求只有一条回路,所以回路能够在这里结束。咱们直接更新答案便可。
状况3,只有一个插头。那么这个插头能够向其余方向走:向下和向右都可以。因此咱们修改一下轮廓线并更新对应状态的答案便可。
值得注意的是,若是一个格子是障碍格,那么当且仅当没有插头连向它时,这才是一个合法状态。由于根据咱们刚才插头的定义:
“值得注意的一点是,插头不是表示将要去某处的虚拟状态,而是表示已经到达某处的现实状态。
也就是说,若是有一个插头指向某个格子,那么这个格子已经和插头来源联通了,咱们接下来要考虑的是从这个插头往哪里走。”
因此,对应障碍格既不能连入插头,也不能连出插头。这一点须要特别注意。
本题的分类讨论仍是相对简单的,在处理完上面的内容后咱们只须要按照上面的思路代码实现便可。
(代码里状态转移颇有意思……)
1 #include <cstdio> 2 #include <cstring> 3 using namespace std; 4 typedef long long LL; 5 int n,m,bin[20],mp[13][13]; 6 LL f[13][13][(1<<12)+10]; 7 inline void Execution(int x,int y) 8 { 9 int plug1=bin[y-1],plug2=bin[y]; 10 for(int j=0;j<bin[m+1];j++) 11 if(mp[x][y]) 12 { 13 f[x][y][j]+=f[x][y-1][j^plug1^plug2]; 14 if( (( j>>(y-1) )&1)== ((j>>(y) )&1) )continue; 15 f[x][y][j]+=f[x][y-1][j]; 16 } 17 else 18 if(!(j&plug1)&&!(j&plug2))f[x][y][j]=f[x][y-1][j]; 19 else f[x][y][j]=0; 20 } 21 int main() 22 { 23 int t;scanf("%d",&t); 24 bin[0]=1;for(int i=1;i<=15;i++)bin[i]=bin[i-1]<<1; 25 for(int u=1;u<=t;u++) 26 { 27 scanf("%d%d",&n,&m); 28 for(int i=1;i<=n;i++) 29 for(int j=1;j<=m;j++) 30 scanf("%d",&mp[i][j]); 31 memset(f,0,sizeof(f));f[1][0][0]=1; 32 for(int i=1;i<=n;i++) 33 { 34 for(int j=1;j<=m;j++)Execution(i,j); 35 if(i!=n)for(int j=0;j<bin[m];j++) 36 f[i+1][0][j<<1]=f[i][m][j]; 37 } 38 printf("Case %d: There are %lld ways to eat the trees.\n",u,f[n][m][0]); 39 } 40 }
经过刚才这道题,你应该已经对插头DP是什么,以及插头DP的基本概念与思想有了基本的了解。
那么下面,咱们经过下一道题来强化分类讨论能力,以及学习对连通性的限制方法。
时间限制:10 s 内存限制:162 MB
Smith在P市的邮政局工做,他天天的工做是从邮局出发,到本身所管辖的全部邮筒取信件,而后带回邮局。
他所管辖的邮筒很是巧地排成了一个m*n的点阵(点阵中的间距都是相等的)。左上角的邮筒刚好在邮局的门口。
Smith是一个很是标新立异的人,他但愿天天都能走不一样的路线,可是同时,他又不但愿路线的长度增长,他想知道他有多少条不一样的路线可走。
你的程序须要根据给定的输入,给出符合题意的输出:
l 输入包括点阵的m和n的值;
l 你须要根据给出的输入,计算出Smith可选的不一样路线的总条数;
输入文件postman.in只有一行。包括两个整数m, n(1 <= m <= 10, 1 <= n <= 20),表示了Smith管辖内的邮筒排成的点阵。
输出文件只有一行,只有一个整数,表示Smith可选的不一样路线的条数。
2 2 说明:该输入表示,Smith管辖了2*2的一个邮筒点阵。
2
有了上一题的经验,不难看出,本题依然是一个在棋盘模型上解决的简单回路问题(简单回路是指起点和终点相同的简单路径)。
而咱们要求的是能一遍遍历整个棋盘的简单回路个数。
但是,若是直接搬用上一题的作法,你会发现一些问题:
好比对于上图的状况,在上一题中这是一个合法解,但在本题中不是。那么咱们就应该思考上题插头定义的片面性在哪里,并想出新的插头定义。
容易观察到,若是每一个格子都在回路中的话,最后全部的格子应该都经过插头链接成了一个连通块。
所以咱们还须要记录每行格子的连通状况.这时咱们就要引入一种新的方法:最小表示法。这是一种用来标记连通性的方法。
具体的过程是:第一个非障碍格子以及与它连通的全部格子标记为1,而后再找第一个未标记的非障碍格子以及与它连通的格子标记为2,……重复这个过程,直到全部的格子都标记完毕.好比连通讯息((1,2,5),(3,6),(4)),就能够表示为{1,1,2,3,1,2}
可是,在实际的代码实现中,这样的最小表示法有些冗余:若是某个格子没有下插头,那么它就不会对下一行的格子产生影响,这个状态就是多余的。
所以,咱们转换优化的角度,用最小表示法来表示插头的联通性:若是这个插头存在,那么就标记这个插头对应的格子的连通标号,若是这个插头不存在,那么标记为0..
在这样优化后,不只状态表示更加简单,并且状态总数将会大大减小.
接下来,咱们用改进过的最小表示法,继续思考上面的问题:如何定义新的插头状态?
若是每一个格子都在回路中的话,咱们还能够获得,每一个格子应该刚好有且仅有2个插头。
咱们来看下面几张图片:
相信细心的你可以发现,轮廓线上方的路径是由若干条互不相交的路径构成的(这是确定的,简单反证:若是最终相交就构不成回路了)。
更有趣的是,每条路径的两个端点刚好对应了轮廓线上的两个插头。
咱们又知道,一条路径应该对应着一个连通块,所以这两个插头同属一个连通块,而且不与其余的连通块联通
而且,咱们在状态转移的时候也不会改变这种性质:
上文的状况1对应着新增一条路径,插头为2;
状况2意味着把2条路径合为一条,联通块变为1个,插头仍是2个;
状况3只有一个插头压根不会改变插头数量。
那么如今咱们知道了,简单回路问题必定知足任什么时候候轮廓线上每个连通份量刚好有2个插头。
互不相交……两个插头……展开你的联想,你能想到什么?
没错,这正是括号匹配!咱们能够按照与括号匹配类似的方式,将轮廓线上每一条路径上中左边那个插头标记为左括号插头,右边那个插头标记为右括号插头。
因为插头之间不会交叉,那么左括号插头必定能够与右括号插头一一对应。
这样咱们就能够解决上面的联通性问题:咱们可使用一种新的定义方式:3进制表示——0表示无插头,1表示左括号插头,2表示右括号插头,记录下全部的轮廓线信息。
可是,值得注意的是,X进制的解码转码是较慢并且较麻烦的。
在空间容许的状况下,建议使用2k进制,而且加上Hash表去重。这样不只能够减小状态,因为仍然可使用二进制位运算,运算速度相比之下也增长了很多。
下面,咱们利用刚才新的插头定义方式来考虑本题的状态转移问题。
依然设当前转移到格子(x,y),设y-1号插头状态为p1,y号插头状态为p2。
状况1:p1==0&&p2==0.
这种状态和上一题的状况1是相似的,咱们只须要新建一个新路径便可:下插头设为左括号插头,右插头设为右括号插头
状况2:p1==0&&p2!=0.
这种状态和上一题的状态3相似,咱们依然能够选择“直走”和“转弯”两种策略
状况3:p1!=0&&p2==0.
这种状态和状况2相似,再也不赘述。
状况4:p1==1&&p2==1.
这种状态把2个左括号插头相连,那么咱们须要将右边那个左括号插头(p2)对应的右括号插头q2修改为左括号插头。
状况5:p1==1&&p2==2.
因为路径两两不相交,因此这种状况只能是本身和本身撞在了一块儿,即造成了回路。
因为只能有一条回路,所以只有在x==n&&y==m时,这种状态才是合法的,咱们能够用它更新答案。
状况6:p1==2&&p2==1.
这种状态至关于把2条路径相连,并无更改其余的插头
状况7:p1==2&&p2==2.
这种状态与状况4类似,这种状态把2个右括号插头相连,那么咱们须要将左边那个右括号插头(p1)对应的左括号插头q1修改为右括号插头。
接下来咱们只要代码实现上述过程便可。
但咱们依然有一个很大的优化点:Hash表的使用。Hash表能够经过去重以及排除无用状态极大的加速插头DP的速度。
Hash表的打法不惟一,下面仅介绍我学习的打法(感谢stdafx学长)
与Hash表相关的主要内容有:
1.mod变量,为Hash表的大小和模数
2.size变量,存储Hash表大小;
3.hash数组,存储某个余数对应的编号
4.key数组,存储状态
5.val数组,存某个状态对应的方案数
在给出一个新状态时,咱们在已有Hash表内搜索是否存在这一状态,若是有,那就修改这个状态对应的val值;若是没有,那就给他新建一个编号
具体的代码实现大概长这样:
1 struct node{int state,next;}; 2 struct Hash_map 3 { 4 int val[MOD],adj[MOD],e;node s[MOD]; 5 inline void intn() 6 { 7 memset(val,0x7f,sizeof(val)),e=0, 8 memset(s,0,sizeof(s)),memset(adj,0,sizeof(adj)); 9 } 10 inline int &operator [] (const int &State) 11 { 12 int pos=State%MOD,i; 13 for(i=adj[pos];i&&s[i].state!=State;i=s[i].next); 14 if(!i)s[++e].state=State,s[e].next=adj[pos],adj[pos]=i=e; 15 return val[i]; 16 } 17 }f[2];
有了Hash表,咱们再来考虑状态转移时的几个小细节:
咱们状态转移的主要工做通常有三个:
1.查询某个插头对应的类型(对应下文Find)
2.查找与某个插头匹配的对应插头(对应下文Link)
3.修改状态中某个插头的类型(对应下文Set)
因为这三个操做很经常使用,因此我把他们写成了函数,方便调用。这三个操做的代码见下:
1 inline int Find(int State,int id){return (State>>((id-1)<<1))&3;} 2 inline void Set(int &State,int bit,int val){bit=(bit-1)<<1;State|=3<<bit,State^=3<<bit,State|=val<<bit;} 3 inline int Link(int State,int pos) 4 { 5 int cnt=0,Delta=(Find(State,pos)==1)?1:-1;//这个变量决定向左寻找匹配仍是向右 6 for(int i=pos;i&&i<=m+1;i+=Delta) 7 { 8 int plug=Find(State,i); 9 if(plug==1)cnt++; 10 else if(plug==2)cnt--; 11 if(cnt==0)return i; 12 } 13 return -1; 14 }
有了上面这些操做,本题的所有代码实现已经水到渠成了:咱们只须要把上面7种状况一一对应实现便可。代码见下:
(还有一个注意点,记得写高精度!)
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <cmath> 5 using namespace std; 6 typedef long long LL; 7 const int cube=(int)1e9,mod=2601; 8 int n,m; 9 struct Data_Analysis 10 { 11 int bit[6]; 12 inline void Clear(){memset(bit,0,sizeof(bit));} 13 Data_Analysis(){Clear();} 14 inline void Set(int t){Clear();while(t)bit[++bit[0]]=t%cube,t/=cube;} 15 inline int &operator [](int x){return bit[x];} 16 inline void Print() 17 { 18 printf("%d",bit[bit[0]]); 19 for(int i=bit[0]-1;i>0;i--)printf("%09d",bit[i]); 20 printf("\n"); 21 } 22 inline Data_Analysis operator + (Data_Analysis b) 23 { 24 Data_Analysis c;c.Clear(); 25 c[0]=max(bit[0],b[0])+1; 26 for(int i=1;i<=c[0];i++) 27 c[i]+=bit[i]+b[i],c[i+1]+=c[i]/cube,c[i]%=cube; 28 while(!c[c[0]])c[0]--; 29 return c; 30 } 31 inline void operator += (Data_Analysis b){*this=*this+b;} 32 inline void operator = (int x){Set(x);} 33 }Ans; 34 struct Hash_Sheet 35 { 36 Data_Analysis val[mod]; 37 int key[mod],size,hash[mod]; 38 inline void Initialize() 39 { 40 memset(val,0,sizeof(val)),memset(key,-1,sizeof(key)); 41 size=0,memset(hash,0,sizeof(hash)); 42 } 43 inline void Newhash(int id,int v){hash[id]=++size,key[size]=v;} 44 Data_Analysis &operator [](const int State) 45 { 46 for(int i=State%mod;;i=(i+1==mod)?0:i+1) 47 { 48 if(!hash[i])Newhash(i,State); 49 if(key[hash[i]]==State)return val[hash[i]]; 50 } 51 } 52 }f[2]; 53 inline int Find(int State,int id){return (State>>((id-1)<<1))&3;} 54 inline void Set(int &State,int bit,int val){bit=(bit-1)<<1;State|=3<<bit,State^=3<<bit,State|=val<<bit;} 55 inline int Link(int State,int pos) 56 { 57 int cnt=0,Delta=(Find(State,pos)==1)?1:-1; 58 for(int i=pos;i&&i<=m+1;i+=Delta) 59 { 60 int plug=Find(State,i); 61 if(plug==1)cnt++; 62 else if(plug==2)cnt--; 63 if(cnt==0)return i; 64 } 65 return -1; 66 } 67 inline void Execution(int x,int y) 68 { 69 int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 70 f[now].Initialize(); 71 for(int i=1;i<=tot;i++) 72 { 73 int State=f[last].key[i]; 74 Data_Analysis Val=f[last].val[i]; 75 int plug1=Find(State,y),plug2=Find(State,y+1); 76 if(Link(State,y)==-1||Link(State,y+1)==-1)continue; 77 if(!plug1&&!plug2){if(x!=n&&y!=m)Set(State,y,1),Set(State,y+1,2),f[now][State]+=Val;} 78 else if(plug1&&!plug2) 79 { 80 if(x!=n)f[now][State]+=Val; 81 if(y!=m)Set(State,y,0),Set(State,y+1,plug1),f[now][State]+=Val; 82 } 83 else if(!plug1&&plug2) 84 { 85 if(y!=m)f[now][State]+=Val; 86 if(x!=n)Set(State,y,plug2),Set(State,y+1,0),f[now][State]+=Val; 87 } 88 else if(plug1==1&&plug2==1) 89 Set(State,Link(State,y+1),1),Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 90 else if(plug1==1&&plug2==2){if(x==n&&y==m)Ans+=Val;} 91 else if(plug1==2&&plug2==1)Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 92 else if(plug1==2&&plug2==2) 93 Set(State,Link(State,y),2),Set(State,y,0),Set(State,y+1,0),f[now][State]+=Val; 94 } 95 } 96 int main() 97 { 98 scanf("%d%d",&n,&m); 99 if(n==1||m==1){printf("1\n");return 0;} 100 if(m>n)swap(n,m); 101 f[0].Initialize();f[0][0]=1; 102 for(int i=1;i<=n;i++) 103 { 104 for(int j=1;j<=m;j++)Execution(i,j); 105 if(i!=n) 106 { 107 int now=(i*m)&1,tot=f[now].size; 108 for(int j=1;j<=tot;j++) 109 f[now].key[j]<<=2; 110 } 111 } 112 Ans+=Ans;Ans.Print(); 113 }
经过这道题的历练,相信你对插头DP的插头定义,最小表示法,以及状态优化的方法有了必定的了解。
尤为须要培养的是插头定义的“手感”,插头定义绝对是你解题的关键。
接下来,咱们把目光转移到“简单路径”上来。经过下面这道例题,相信会对这类简单路径&回路问题有更深的理解。
Hnoi2007-Day1有一道题目 Park:给你一个 m * n 的矩阵,每一个矩阵内有个
权值V(i,j) (可能为负数),要求找一条回路,使得每一个点最多通过一次,而且通过
的点权值之和最大,想必你们印象深入吧.
无聊的小 C 同窗把这个问题稍微改了一下:要求找一条路径,使得每一个点
最多通过一次,而且点权值之和最大,若是你跟小 C 同样无聊,就麻烦作一下
这个题目吧.
第一行 m, n,接下来 m行每行 n 个数即V( i,j)
一个整数表示路径的最大权值之和.
1 inline bool check(int State) 2 { 3 int cnt=0,cnt1=0; 4 for(int i=1;i<=m+1;i++) 5 { 6 int plug=Find(State,i); 7 if(plug==3)cnt++; 8 else if(plug==1)cnt1++; 9 else if(plug==2)cnt1--; 10 } 11 return cnt>2||cnt1!=0; 12 }
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N=110,M=10,mod=16631; 6 int n,m,ans,v[N][M]; 7 struct Hash_System 8 { 9 int key[mod],size,hash[mod],val[mod]; 10 inline void Initialize() 11 { 12 memset(key,-1,sizeof(key));memset(val,0xaf,sizeof(val)); 13 size=0;memset(hash,0,sizeof(hash)); 14 } 15 inline void Newhash(int id,int State){hash[id]=++size;key[size]=State;} 16 inline int &operator [] (const int State) 17 { 18 for(int i=State%mod;;i=(i+1==mod)?0:i+1) 19 { 20 if(!hash[i])Newhash(i,State); 21 if(key[hash[i]]==State)return val[hash[i]]; 22 } 23 } 24 }f[2]; 25 inline int max(int a,int b){return a>b?a:b;} 26 inline int Find(int State,int pos){return (State>>((pos-1)<<1))&3;} 27 inline void Set(int &State,int pos,int val){pos=(pos-1)<<1,State|=(3<<pos),State^=(3<<pos),State^=(val<<pos);} 28 inline int Link(int State,int pos) 29 { 30 int cnt=0,Delta=(Find(State,pos)==1)?1:-1; 31 for(int i=pos;i&&i<=m+1;i+=Delta) 32 { 33 int plug=Find(State,i); 34 if(plug==1)cnt++; 35 else if(plug==2)cnt--; 36 if(cnt==0)return i; 37 } 38 return -1; 39 } 40 inline bool check(int State) 41 { 42 int cnt=0,cnt1=0; 43 for(int i=1;i<=m+1;i++) 44 { 45 int plug=Find(State,i); 46 if(plug==3)cnt++; 47 else if(plug==1)cnt1++; 48 else if(plug==2)cnt1--; 49 } 50 return cnt>2||cnt1!=0; 51 } 52 inline void Execution(int x,int y) 53 { 54 int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 55 f[now].Initialize(); 56 for(int i=1;i<=tot;i++) 57 { 58 int State=f[last].key[i],Val=f[last].val[i]; 59 if (check(State)||State>=(1<<((m+1)<<1)))continue; 60 int plug1=Find(State,y),plug2=Find(State,y+1); 61 int ideal=State;Set(ideal,y,0),Set(ideal,y+1,0);//ideal表明去掉y-1,y两个插头以后的轮廓线状态。 62 int empty1=ideal,empty2=ideal; 63 if(!plug1&&!plug2) 64 { 65 f[now][ideal]=max(f[now][ideal],Val); 66 if(x<n&&y<m)Set(State,y,1),Set(State,y+1,2),f[now][State]=max(f[now][State],Val+v[x][y]); 67 if(x<n)Set(empty1,y,3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 68 if(y<m)Set(empty2,y+1,3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 69 } 70 else if(plug1&&!plug2) 71 { 72 if(x<n)f[now][State]=max(f[now][State],Val+v[x][y]); 73 if(y<m)Set(empty1,y+1,plug1),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 74 if(plug1==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 75 else Set(empty2,Link(State,y),3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 76 } 77 else if(!plug1&&plug2) 78 { 79 if(y<m)f[now][State]=max(f[now][State],Val+v[x][y]); 80 if(x<n)Set(empty2,y,plug2),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 81 if(plug2==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 82 else Set(empty1,Link(State,y+1),3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 83 } 84 else if(plug1==1&&plug2==1)Set(empty1,Link(State,y+1),1),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 85 else if(plug1==1&&plug2==2)continue; 86 else if(plug1==2&&plug2==1)f[now][ideal]=max(f[now][ideal],Val+v[x][y]); 87 else if(plug1==2&&plug2==2)Set(empty2,Link(State,y),2),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 88 else if(plug1==3&&plug2==3){if(!ideal)ans=max(ans,Val+v[x][y]);} 89 else if(plug2==3)Set(empty1,Link(State,y),3),f[now][empty1]=max(f[now][empty1],Val+v[x][y]); 90 else if(plug1==3)Set(empty2,Link(State,y+1),3),f[now][empty2]=max(f[now][empty2],Val+v[x][y]); 91 } 92 } 93 int main() 94 { 95 scanf("%d%d",&n,&m); 96 for(register int i=1;i<=n;i++) 97 for(register int j=1;j<=m;j++) 98 scanf("%d",&v[i][j]),ans=max(ans,v[i][j]); 99 f[0].Initialize();f[0][0]=0; 100 for(register int i=1;i<=n;i++) 101 { 102 for(register int j=1;j<=m;j++)Execution(i,j); 103 if(i!=n) 104 for(int j=1,last=(i*m)&1,tot=f[last].size;j<=tot;j++) 105 f[last].key[j]<<=2; 106 } 107 printf("%d\n",ans); 108 }
lxhgww的小名叫“小L”,这是由于他老是很喜欢L型的东西。小L家的客厅是一个矩形,如今他想用L型的地板来铺满整个客厅,客厅里有些位置有柱子,不能铺地板。如今小L想知道,用L型的地板铺满整个客厅有多少种不一样的方案?
须要注意的是,以下图所示,L型地板的两端长度能够任意变化,但不能长度为0。铺设完成后,客厅里面全部没有柱子的地方都必须铺上地板,但同一个地方不能被铺屡次。
输入的第一行包含两个整数,R和C,表示客厅的大小。
接着是R行,每行C个字符。’_’表示对应的位置是空的,必须铺地板;’*’表示对应的位置有柱子,不能铺地板。
输出一行,包含一个整数,表示铺满整个客厅的方案数。因为这个数可能很大,只需输出它除以20110520的余数。
R*C<=100
看到这道题,咱们很容易发现上面几道题的全部插头定义方式都不在适用了。
所以,咱们须要自行寻找到新的插头定义方式。
容易注意到,和上题的“路径”相比,本题的合法路径“L型地板”有一些特殊的地方:拐弯且仅拐弯一次。
这因为一条路径只有两种状态:拐弯过和没拐弯过,所以咱们能够尝试着这样定义新的插头:
咱们使用三进制,0表明没有插头,1表明没拐弯过的路径,2表明已经拐弯过的路径。
依然设当前转移到格子(x,y),设y-1号插头状态为p1,y号插头状态为p2。
那么会有下面的几种状况:
状况1:p1==0&&p2==0
这时咱们有三种可选的策略:
①以当前位置为起点,从p1方向引出一条新的路径(把p1修改成1号插头)
②以当前位置为起点,从p2方向引出一条新的路径(把p2修改成1号插头)
③以当前位置为“L”型路径的转折点,向p1,p2两个方向均引出一个2号插头.
状况2:p1==0&&p2==1
因为p2节点尚未拐过弯,所以咱们有2种可选的策略:
①继续直走,不拐弯,即上->下(把p1修改成1号插头,p2置0)
②选择拐弯,即上->右(把p2改成2号插头)
状况3:p1==1&&p2==0
这种状况和状况2相似,再也不赘述。
状况4:p1==0&&p2==2
因为p2节点已经拐过弯,因此咱们有以下的两种策略:
状况5:p1==2&&p2==0
这种状况与状况4相似,再也不赘述。
状况6:p1==1&&p2==1
这种状况下,两块地板均没有拐过弯,所以咱们能够在本格将这两块地板合并,造成一个合法的“L”型路径,并将本格看作他们的转折点。(把p1和p2都置为0)
至此,新插头定义的状态转移已经讨论完成。
不难发现,这种新的插头定义能够处理可能发生的全部可行状况。
咱们只须要把他们转化为代码实现便可,具体见下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 const int N=105,HASH=200097,mod=20110520; 6 int ans,n,m,last_x,last_y;char c[N][N];bool room[N][N]; 7 struct Hash_System 8 { 9 int val[HASH],key[HASH],hash[HASH],size; 10 inline void Initialize() 11 { 12 memset(val,0,sizeof(val)),memset(hash,0,sizeof(hash)); 13 memset(key,-1,sizeof(key)),size=0; 14 } 15 inline void Newhash(int id,int State){hash[id]=++size,key[size]=State;} 16 inline int &operator [] (const int State) 17 { 18 for(register int i=State%HASH;;i=(i+1==HASH)?0:i+1) 19 { 20 if(!hash[i])Newhash(i,State); 21 if(key[hash[i]]==State)return val[hash[i]]; 22 } 23 } 24 }f[2]; 25 inline int Find(int State,int pos){return (State>>((pos-1)<<1))&3;} 26 inline void Set(int &State,int pos,int val) 27 {pos=(pos-1)<<1,State|=(3<<pos),State^=(3<<pos),State^=(val<<pos);} 28 inline void Print(int State){for(int i=0;i<=m;i++)printf("%d",(State>>(i<<1))&3);} 29 inline void Execution(int x,int y) 30 { 31 register int now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 32 f[now].Initialize(); 33 for(register int i=1;i<=tot;i++) 34 { 35 int State=f[last].key[i],Val=f[last].val[i]; 36 int plug1=Find(State,y),plug2=Find(State,y+1); 37 if(room[x][y]) 38 { 39 if(!plug1&&!plug2) 40 { 41 if(room[x+1][y])Set(State,y,1),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 42 if(room[x][y+1])Set(State,y,0),Set(State,y+1,1),f[now][State]=(f[now][State]+Val)%mod; 43 if(room[x][y+1]&&room[x+1][y])Set(State,y,2),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 44 } 45 else if(!plug1&&plug2) 46 { 47 if(plug2==1) 48 { 49 if(room[x+1][y])Set(State,y,1),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 50 if(room[x][y+1])Set(State,y,0),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 51 } 52 else 53 { 54 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 55 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 56 if(room[x+1][y])Set(State,y,2),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 57 } 58 } 59 else if(plug1&&!plug2) 60 { 61 if(plug1==1) 62 { 63 if(room[x][y+1])Set(State,y,0),Set(State,y+1,1),f[now][State]=(f[now][State]+Val)%mod; 64 if(room[x+1][y])Set(State,y,2),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 65 } 66 else 67 { 68 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 69 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 70 if(room[x][y+1])Set(State,y,0),Set(State,y+1,2),f[now][State]=(f[now][State]+Val)%mod; 71 } 72 } 73 else if(plug1==1&&plug2==1) 74 { 75 Set(State,y,0),Set(State,y+1,0),f[now][State]=(f[now][State]+Val)%mod; 76 if(x==last_x&&y==last_y&&!State)ans=(ans+Val)%mod; 77 } 78 } 79 else if(!plug1&&!plug2)f[now][State]=(f[now][State]+Val)%mod; 80 } 81 } 82 int main() 83 { 84 scanf("%d%d",&n,&m); 85 for(register int i=1;i<=n;i++)scanf("%s",c[i]+1); 86 for(register int i=1;i<=n;i++) 87 for(register int j=1;j<=m;j++)room[i][j]=(c[i][j]=='*')?0:1; 88 if(m>n) 89 { 90 for(register int i=1;i<=n;i++) 91 for(register int j=i+1;j<=m;j++) 92 swap(room[i][j],room[j][i]); 93 swap(n,m); 94 } 95 for(register int i=1;i<=n;i++) 96 for(register int j=1;j<=m;j++) 97 if(room[i][j])last_x=i,last_y=j; 98 f[0].Initialize();f[0][0]=1; 99 for(register int i=1;i<=n;i++) 100 { 101 for(register int j=1;j<=m;j++)Execution(i,j); 102 if(i!=n) 103 for(register int j=1,last=(i*m)&1,tot=f[last].size;j<=tot;j++) 104 f[last].key[j]<<=2; 105 } 106 printf("%d\n",ans); 107 }
面对一道以前没有见过的问题,咱们经过寻找插头的新定义成功解决了该题。
相信你已经对插头定义有了初步的了解,接下来,必定要在作题时继续培养这种能力,这样才能百战不殆。
下面,再给出几道不是那么简单的题目,有兴趣的读者能够试着作一下:
BZOJ1187.[HNOI2007]神奇游乐园
BZOJ2595[Wc2008]游览计划
POJ3133Manhattan Wiring
POJ1739 Tony's Tour
上面讲解的4道题都是插头DP中比较基础的问题,但各自都体现出了不一样的侧重点。
插头DP是一类很美妙的问题,当你看到本身辛苦想出、码出的分类讨论AC的时候,心中必定会有很大的成就感吧!
但愿读完这篇博文的你能有所收获,对插头DP有更深入的了解!