生成树
生成树(jdoj-1181)
题目大意:给你一个网格状的$N\cdot 1$的矩形,有2(N+1)个顶点,3(N-1)+4条边。问:这个图有多少生成树。
注释:N<=1000,答案mod12345
想法:懵逼了......这啥啊,完全不懂。说实话,这是我当了OIer之后做的坠难的一道递推题(之前的不算),这题乍眼一看就是递推。
我们做递推题时有一种极其重要的思想,就是我们要将那个被求的集合或式子进行一个分划。
什么是分划呢?比如一个总集合S,$A_1$到$A_n$是集合S的分划当且仅当${A_1}\cap {A_2}\cap {A_3}\cap...\cap{A_n}=\{\varnothing\}$和${A_1}\cup {A_2}\cup {A_3}\cup...\cup{A_n}=S$同时成立时,我们就说这n个小集合是总集合S的一个分划。比如说奇数与偶数就是整数的一个分划。这是递推中的一种思想。比如说我博客中的提到圆盘染色的那道题,那就是一个分划。为什么它是一种分划呢?因为任意一种满足条件的染色方案的倒数第二个扇形要么与起始扇形同色,要么与起始扇形异色,并没有第三种方式。所以,这就是一种分划。分划在递推问题中非常实用,为什么?因为这几个部分在现在来讲是没有交集的,也就是说这几个东西都可以单独处理。就算每一部分的答案可能都是其他数之前情况的组合或排列,但到了那个时候,这个在思想层面的问题就变成了一个笔与纸的交融——解方程。方程的次数不知道,是几元方程也不知道,但我们知道只要可以变成方程的形式就是可解的,就不再是那种虚无缥缈的鬼玩意儿。但是,分划的选取很重要。有的题可以选择不分划,也是可以的。
但是这道题,分划是我们唯一的突破口。显然,最最容易想到的就是最后一个竖线的选取或不选取,但是,这样的分划是极其鸡肋的。每一次在递推的过程中都会经历的事情在这两个分划的结果中并没有达到我们想要的效果。在此,我们不妨把视野扩大——观察从(i-1)个矩形到 i 个矩形所产生出来的3条边,看这三条边的所有选取情况。这三条边中,每条边有选或不选,共计8种情况,我们考虑哪些情况是不可能被选取的:1.全不选。2.只选那个竖线。为什么不满足题意?因为只要出现这样的情况,在第i个矩形到第n个矩形的连边中,无论进行怎样的操作,都不可能使得从第i-1个过渡到第i个时所加的两个点与前面的点联通。所以这两种是一定要被除掉的。
那么,我们还剩6种情况。而这6种情况,我们可以看做是一种"类分划"。接下来,我们对每一种情况单独考虑:我们将新加入的三条边按顺时针依次标记为1、2、3,其中,1和3是等价的。考虑如果选取一种方案,前面的选取情况。这显然是复杂的。所以,我们可以用一种极其强的分划处理手段:
将分划后的小集合重新组合成另一种分划。而这种与原来的区别在于,新分划出来的几个大集合可以进行二次分划。
这就好办了。我们这次的分划极其猛的分划(在此鸣谢CQzhangyu)——我们将新加入的两个点分开。上面的点所连接的是上面的联通块,下面的点所连接的是下面的联通块。我们考虑这两个联通块是否连通便是一种分划。我们用函数f[i]和g[i]分别别表示连通和不连通。
那么,回顾刚才的想法,如果选取{1,2},那么前面n个点必须连通!为什么?因为如果前面的点并不连通,所以第i-1个矩形的最后两个点无论如何也不可能连通!因为他们的联通路不可能在前i-1个矩形中完成,而且我们并没有选取{1,2.3},所以这两个点右侧也是不合理的。所以,这两个点不可能连通,这与树的定义相违背。所以,此时,我们只可能从f更新到f,只选取{3,2}和{1,2}是一样的,所以,现在有两个f[i-1]更新f[i]。
下面我们考虑{1,3},因为此时新进的两个点不连通,而且这两个点都与前面的大矩形连通。所以,此时的两个点的是否连通由前面的点的连通性相同。所以,我们可以从g更新到g,也可以从f更新到f,此时,f[i]=3*f[i-1],g[i]=g[i-1]。下面我们考虑{1},这时新进的两个点是不连通的,如果此时前面的点不连通,新进点中下面的点的左边的点是没有办法和它上面的点联通的。所以,此时,我们只能从f更新到g,此时,g[i]+=f[i-1]。只选取{3}和{1}相同,所以此时,g[i]=2*f[i-1]+g[i-1]。
最后一种情况:选取{1,2,3},此时,我们发现,第i-1个矩形最左侧的两个点本就是连通的。我们考虑它们的左侧,如果这是连通的,那么我们发现,下面的点可以走左侧到达上面的点,也可以从右侧到达。我们回顾环:可以自己向外连边回到自己,另一种定义方式就是环上任意一点有至少两条路到达环上另一点,这个推论反之也成立,所以我们发现:此时出现了一个环——与树的定义相违背。所以,此时只能从g更新到f,此时f[i]=3*f[i-1]+g[i-1]。我们所有的情况都讨论完了,发现此时:f[i]=3*f[i-1]+g[i-1],g[i]=2*f[i-1]+g[i-1]。
此时,我们他妈的终于做完了这道要死不死的题了......
最后,附上丑陋的代码......
1 #include <iostream> 2 #include <cstdio> 3 const int mod=12345; 4 using namespace std; 5 int f[1010],g[1010]; 6 int main() 7 { 8 int n; 9 scanf("%d",&n); 10 f[0]=1,g[0]=1; 11 for(int i=1;i<=n;i++) 12 { 13 f[i]=(3*f[i-1]%mod+g[i-1]%mod)%mod; 14 g[i]=(2*f[i-1]%mod+g[i-1]%mod)%mod; 15 } 16 printf("%d",f[n]); 17 return 0; 18 }
小结:这题是极其有意思的。我们在每一次的推导都能得到收获。考虑分划在递推中的重要性,以及二次分划在递推中的强大。统计答案时一定不要错(博主统计错了啊!)
这篇本来不冷不热的题被我刨根问底,码了整整1个小时,严禁转载,除非你即将超生...——未经博主允许,严禁转载!
| 欢迎来原网站坐坐! >原文链接<