[07/18NOIP模拟测试5]超级树

鬼能想到的dp定义:dp[i][j]表示在一棵i级超级树中,有j条路径同时存在且这j条路径没有公共点时,可能的情况数

刚开始我也没看懂,所以举个例子

如一个2级的超级树,父节点为1,左右儿子为2,3

(感谢Al_Ca贡献的图,但我感觉题目里给的这个带编号更好一些。懒得把字去掉了将就着看吧)

(感谢xkl贡献的截好的图,但我斟酌了一下带上样例解释你们是不是会更好理解啊~)

(我太挑剔啦,不用给我发图啦,谢谢大家)

dp[2][1]=9,因为2级树里的路径一共有9条(样例),显然只有一条路径的话肯定没有公共点

dp[2][2]=7,(1,2)(1,3)(2,3)(2,13)(2,31)(3,12)(3,21),注意路径方向不同就是不同的

//公告:原数有误,感谢starsing边骂我边更正

dp[2][3]=1,只有(1单独作为一条路径,2单独作为一条路径,3单独作为一条路径,共3条)这一种可能。它们没有公共点。

dp[2][4]=0,2级树只有3个点,每条路径至少1个点,所以4条路径不可能没有公共点

那么按照套路。。。递推肝它!

如果你想要得到dp[i][j],该从什么转移过来?

情况太多了,想不出来。。。

我们尝试把dp[i][j]的情况设为已知,往后推别的

把两棵i-1级的树,配上一个根节点,合成一个i级的树,合成过程中的方案?

枚举左右子树中各有多少路径,左子树j右子树k,设sum=dp[i][j]*dp[i][k]

首先,想最简单的玩意:与根节点无关,左右子树保持原样

那么整个图中的路径数并没有变,左子树还是j个,右子树k个,只不过整个树升级了,根据乘法原理

dp[i+1][j+k]+=dp[i][j]*dp[i][k],即dp[i+1][j+k]+=sum,就这样累加方案数就好了

下一种情况,你随便从左子树里选一条边,把这条边的终点和父节点相连,相连之后的新路径仍然与右子树当中的任何一条路径没有交点

左边的路径增长了1,但是路径的总数没有变,还是(左子树+根)有j条,右子树有k条,从左子树j个里挑出一个的方案数是j

dp[i+1][j+k]+=sum*j;    ----(2-1-1)

然后我们既然可以把终点延伸到父节点,我们也可以把起点延伸到父节点

我们还是从左子树中随便挑路径,把根节点连向它,同上,路径延长而总数不变

dp[i+1][j+k]+=sum*j;    ----(2-2-1)

你现在一直是在左子树里挑边,为什么不在右子树里也挑呢?全部同理。不过是j变成了k

dp[i+1][j+k]+=sum*k;    ----(2-1-2)

dp[i+1][j+k]+=sum*k;    ----(2-1-2)

合并2打头的这四个式子:dp[i+1][j+k]+=2*sum*(j+k);

考虑下一种情况:左子树有j条右子树有k条,可是父节点它本身还是一条啊

那么总路径数就是(左j+右k+根1)=j+k+1

dp[i+1][j+k+1]+=sum;

再下一种情况:我们从左子树中选出一条路径,把它的终点连向父节点,在从根节点连向右子树的一条路径的起点

总路径数是j+k-1,因为你把两条合成了一条

还是举例子,若左子树有三条路径ABC,右子树3条abc。合成A和根再到a,现在的路径就是A-a, B, C, b, c,为3+3-1=5条。

而选边的方法数是j*k,这个是左连根再连右。同理还有右连根再连左,所以总数*2.

dp[i+1][j+k-1]+=sum*2*j*k;

最后一种情况,也是最恶心的:左子树一条路径终点连向父节点,再由父节点连向左子树的另一条路径的起点

证明可行性:因为dp含义中已经定义,这些路径彼此没有交点,那么从中任选两条路径后,与根节点按上述方法相连后仍然这一条路径自身没有公共点,与其他边仍然没有交点,满足条件。

dp[i+1][j+k-1]+=sum*j*(j-1)  (5-1)

因为是要在l条路径中选出两条不一样的,且与顺序有关不用除2

(你可以认为是选出的第一个路径的起点作为合成路径的起点,第二条路径的终点作为合成路径的终点,那么交换这两条路径所得到的合成路径是不同的)

同理,从右边选两条:dp[i+1][j+k-1]+=sum*k*(k-1)  (5-2)

合并一下:dp[i][j+k-1]+=sum*(j*(j-1)+k*(k-1));

好了,没有其他情况了。5个蓝色的式子就是全部的递推式子,不重不漏。

初始状态dp[1][0]=dp[1][1]=1;

最后的答案就是dp[n][1]了,即n级树中全部的路径数,没有相交之类的限制(所以第二维选出一条路径即可)

一定一定要多取模!

完事!

但是你A不了,对不对?

主要有两种错误,稍讲一下。

TLE :里面可能夹杂了WA,真的。

卡常,加法的取模优化,这还不够。但是有必要!

你仔细想一下枚举范围,i肯定是到n了,那么j和k呢?

能给dp[m][q]贡献答案的,是dp[m-1][?],问号如果是大于q+1,显然就没用了。即两维之和不超过m+q

所以为了求出dp[n][1],那么两维之和就不必超过n+1。所以对j的限制就是0~(n-i+2)

那么对k的限制就更紧了,0~(n-i+2-j)

还是不够?前期的某些枚举是无效的,如k=300,你会枚举到dp[1][250]之类的

1级树里怎么可能会有250条路径嘛!

因此对枚举的上届再次限制,在满足上一个条件的前提下,还要限制j.k<=2i-1

顺便再提一下zkt的常数减半的优化:因为j和k同步的枚举,颠倒它们的顺序会累加完全一样的答案

那么就干脆把sum加倍,强制k从j+1开始枚举,特殊处理j==k即可。

WA 95了?(或者因为有T的点被显示成了WA60之类)

友情赠送测试点“1 1”

我并没有经历这个问题,因为我输出的时候顺手取模了。

不要小瞧这5分!这很关键!要长记性!

然而。。。有一个最重要问题没有解决,这个思路是怎么想到的?在题解的开头我说它很难想到。

可是“如何想到”这很关键! 否则你只会看到别人的dp定义才会做,考试还是什么也不会。

思维这个东西很难讲清楚,但我还是要冒险说一说。[不喜勿喷]

至少,i级树从i-1级树中推来的这个分解问题这个思路应该还是能够想到的吧?

那么从这个角度出发分情况,应该是可以想到上述5种情况的。

在要用父节点拼接两条路径时,我们可以发现如果设的dp含义中如果不限制让它没有公共点,那么拼接起来无法计算!

这样也许就会可以想到了吧。。。反正我没想到。。。还要加油啊

接下来含义中已经定义了没有交点。

我们观察数据范围,应该是n3左右的,最外层枚举树的等级没什么问题,里面呢?

我们将样例输出2即245分解质因数(常用思路),发现出现了较大的质数,猜测这题不是简单粗暴的相乘,而是某些东西相乘再求和

这样我们就有了一个大体的框架(当然到这时候你还想不到限制枚举范围什么的)

1 for(int i=1;i<n;++I)//表示树的等级
2     for(int j=1;j<=n;++j)//含义未知
3         for(int k=1;k<=n;++k)//含义未知
4             dp[i+1][?]+=dp[i-1][j]*dp[i-1][k]*(?);

现在我们只需要猜测出那两个位置的含义是什么,式子中的问号就能凿实了。

还有什么的限制是n级别的?说实在的我的第一反应是已经用在路径中的点的数量。

但是我们可以发现每个点之间并不是平等的,它们的连边关系不同,故笼统地用它们的数量来推是不现实的。

点不行,还能是啥?只能是边了,而且没有交点。说真的,不太好想。

但是边之间的确彼此平等。。。枚举范围的确是n级别。。。

所以你就想到了。。。

好吧,我承认,有点牵强,但是的确有人能在考场上自己想出来,他们太强了,所以咱们更要练啊!

最后,给一个建议,看完了博客去打题之前,你最好再系统地理解一下,争取码代码的时候不要再回来看公式。

自己根据含义想出来,自己推式子,理解消化,做题才有意义。

记住,你不是公式的搬运工。

最后送给你们几个调试用小样例(不取模)

1 1

2 9

3 245

4 126565

5 32054326261

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 #define int long long
 5 #define sum (dp[i][l]*dp[i][r])%mod
 6 int dp[301][602],n,k,mod;
 7 inline void modd(long long &a){if(a>=mod)a-=mod;}
 8 signed main(){
 9     scanf("%lld%lld",&n,&mod);
10     dp[1][0]=dp[1][1]=1;
11     for(int i=1;i<n;++i) for(int l=0;l<=min(n-i+2,i<=9?(1ll<<i)-1:n-i+2);++l) for(int r=0;r<=min(n-i+2-l,i<=9?(1ll<<i)-1:n-i+2);++r){
12         modd(dp[i+1][l+r]+=sum);//直接转移
13         modd(dp[i+1][l+r+1]+=sum);//只多了一个根自己是路径
14         modd(dp[i+1][l+r]+=(sum<<1)*(l+r)%mod);//只把一条边伸长到根节点,双向
15         modd(dp[i+1][l+r-1]+=l*r%mod*(sum<<1)%mod);//左右通过根节点相连
16         modd(dp[i+1][l+r-1]+=sum*(l*(l-1)%mod+r*(r-1)%mod)%mod);//同侧通过根节点相连
17     }
18     printf("%lld
",dp[n][1]%mod);
19 }
考时垃圾,考后牛逼。。。有什么用
原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11208439.html