题解 超级树

传送门

这转移是人想的?
这个转移是真的想不出来……有空找个拓宽思路的dp题单康康?(咕)

考场上又一次以为是组合数,好在后来反应过来是个dp但是不会写,最后打表拿了15pts
考虑转移,分析深度+1会对方案数造成什么影响
\(dp[i]\)表示深度为i的超级树的路径数
深度已知,则含有的点数可求,考虑用组合数处理增加的连边方案
然后发现因为向子树连边时子树每个点曾连出的边也会影响方案数,就不会转移了

然后考完试着拓展了一下这个思路:
\(dp[i]\)表示深度为i的超级树的路径数,再令\(cnt[i][j]\)为在i-超级树中以一个深度为j的点为起点的路径数
首先深度+1, \(dp[i+1]+=2*dp[i]\)
然后考虑新增的这个根节点带来的方案数
以新根节点为始,长度为1方案数(就是直连根节点和已有点):\(dp[i+1]+=2^i-1\)
考虑跨根节点连边:枚举两边始末点深度即可
然而不跨根节点,从同一子树向根节点连出两条边炸掉了
这里有个问题:只知两个以深度为j, k的点为起点的路径数,无法在把这两条路径连通后仍保证经过节点不重复
如果不允许从同一子树向根节点连出两条边貌似就能用这种方法了,不过时间复杂度并不会更优
所以考虑处理「从新根节点向点连边」炸掉了,还是因为只考虑每个点连出的边的方案数,而实际上具体方案间不满足直接合并

然后正解:
那就考虑边,令\(dp[i][j]\)为在i-超级树中选出j条深度不同且满足经过点不重复条件的路径的方案数
理解状态设计花了我20min
注意这里在转移时可以连通两条边,使它们连为一条,所以边数每次至多减1,k次转移至多减k,所以l和r枚举到k即可
转移基本一样,枚举左右子树所选的边的类型,注意「点连本身也算一条路径」和「这里每条边都是不同的」即可
为了卡常,令\(num=dp[i][l]*dp[i][r]\)
不加边:\(dp[i+1][l+r]+=num\)
根节点自身连边(其它边选法任意):\(dp[i+1][l+r+1]+=num\)
根节点向两子树连边:\(dp[i+1][l+r]+=2*num*(l+r)\)
两子树间跨根节点连边:\(dp[i+1][l+r-1]+=2*num*l*r\)
同一子树内通过根节点连边:\(dp[i+1][l+r-1]+=2*num*(C(l, 2)+C(r, 2))\)
\(dp[i+1][l+r-1]+=num*(l*(l-1)+r*(r-1))\)
我这里之前一直没理解对 居然现在才发现 暴躁.jpg
我一直理解的是可选的连边少了一条,但实际上是组合数式子拆开了……
当晚upd: 之前的理解方法也是对的……就是可选边少了一条,所以是\(l*(l-1)\),这里因为处理l和r时相当于考虑了顺序就不用乘2了
yysy,这个解法其实和考虑点解法差不多吧(雾

再说优化:
边数每次至多减1,显然\(l+r \geqslant k\)就没用了
而且考虑枚举到深度为i时,还有\((k-i+1)\)轮,所以这时\(l \geqslant k-i+1\)就没用了,和上一条同时用上更优,可以1800ms

而且这题卡细节颇多,考虑对1取模,一定等于0
而这里\(dp[1][1]=1\),直接输出会炸,所以输出时记得再取一次模

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 310
#define ll long long 
#define ld long double
#define usd unsigned
#define ull unsigned long long
//#define int long long 

int k;
ll mod, dp[N][N];
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	ll num;
	
	scanf("%d%lld", &k, &mod);
	dp[1][0]=dp[1][1]=1;
	for (int i=1,lim; i<k; ++i) 
		for (int l=0,lim=k-i+1; l<=k-i+1; ++l,--lim) 
			for (int r=0; r<=lim; ++r) {
				//cout<<i<<' '<<l<<' '<<r<<endl;
				num=dp[i][l]*dp[i][r]%mod;
				md(dp[i+1][l+r], num);
				md(dp[i+1][l+r+1], num);
				md(dp[i+1][l+r], 2ll*num*(l+r)%mod);
				md(dp[i+1][l+r-1], 2ll*num*l*r%mod);
				md(dp[i+1][l+r-1], num*(l*(l-1)+r*(r-1))%mod);
			}
	printf("%lld\n", dp[k][1]%mod);

	return 0;
}
posted @ 2021-06-19 17:41  Administrator-09  阅读(22)  评论(0编辑  收藏  举报