专栏文章

题解:P13020 [GESP202506 八级] 遍历计数

P13020题解参与者 1已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@mip0b86n
此快照首次捕获于
2025/12/03 04:04
3 个月前
此快照最后确认于
2025/12/03 04:04
3 个月前
查看原文
我居然是第一个写题解的!!!

此题解思路为树形DP

题意

给定一棵,求出它有多少个dfs序(这个dfs序根节点和下一个遍历的节点都不一定)

思路

这里给大家一个巧妙的思路
设置 fif_i为以1为根节点,i以及它的后辈构成的这棵树dfs序的数量
设置一个存储边的 vector:e
不难发现:
  • fi=ei.sizei=0fei,j×Aei.sizeei.sizef_i = \prod_{e_i.size}^{i=0}f_{e_{i,j}} \times A_{e_i.size}^{e_i.size}
再推一下,也就是:
  • fi=ei.sizei=0fei,j×(ei.size)!f_i = \prod_{e_i.size}^{i=0}f_{e_{i,j}} \times (e_i.size)!
这样, O(n2)O(n^2)的代码就出现了。
退出了这个公式的我立马就变写出了下面的代码
推荐看一眼
CPP
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9;
int n,f[100005],lstf[100005],fact[100005];
long long ans;
vector<int> e[100005];
void dfs(int now,int fath,int root){
	f[now] = 1;
	int sz = e[now].size();
	for (int i = 0;i < sz;i++){
		int son = e[now][i];
		if (son == fath) continue;
		dfs(son,now,root);
		f[now] = 1ll * f[now] * f[son] % mod;
	}
	if (now == root) f[now] = 1ll * f[now] * fact[sz] % mod;
	else if (sz > 1) f[now] = 1ll * f[now] * fact[sz-1] % mod;//注意,这里sz-1是因为还有父亲节点,根节点没有父亲,所以不用减
}


int main(){
	scanf("%d",&n);
	fact[1] = fact[0] = 1;
	for (int i = 2;i <= 100000;i++) fact[i] = 1ll * fact[i-1] * i % mod;//预制
	for (int i = 1;i < n;i++){
		int u,v;scanf("%d%d",&u,&v);
		e[u].push_back(v);e[v].push_back(u);
	}
	for (int i = 1;i <= n;i++){
		dfs(i,-1,i);
		ans = (ans + (long long)f[i]) % mod;
	}
	cout << ans;
}
风风火火的交上去后,直接T飞~~
为什么呢?
一看到n1e5n \geq 1e5直接释怀了
那怎么办呢?
在考场上的我写满了三页草稿纸,终于想出来一种O(2n)O(2n)的做法
因为是动态规划,所以当前now节点的父亲节点fath一定知道它的实际答案
而且,节点now作为根节点时,少算的只有它的父亲节点,那对于now为根节点时,父亲节点的那一块就是
  • ffathfnow×efath.size\frac{f_{fath}}{f_{now} \times e_{fath}.size}
然后,除了父亲节点那一块,其他节点也在之前处理过了,就是fnowf_{now},根据我们之前推出来的公式:fi=ei.sizei=0fei,j×(ei.size)!f_i = \prod_{e_i.size}^{i=0}f_{e_{i,j}} \times (e_i.size)!
就可以推出以now为根节点的公式
  • ffathfnow×efath.size×fnow×enow.size\frac{f_{fath}}{f_{now} \times e_{fath}.size} \times f_{now} \times e_{now}.size
约分后得出最终公式:
  • ffath×enow.sizeefath.size\frac{f_{fath} \times e_{now}.size}{e_{fath}.size}
那我们怎么计算出这个算式呢?
我们可以提前计算一下ffathefath.size\frac{f_{fath}}{e_{fath}.size}
将这个东西叫做lstflstf
然后经过计算可以得出lstfnow=lstffathlstf_{now} = lstf_{fath}
最后这个算式就是f[son]=1ll×lstfnow×eson.sizef[son] = 1ll \times lstf_{now} \times e_{son}.size(我这里是从now传递给son)
最后就是喜闻乐见的代码环节了
CPP
#include<bits/stdc++.h>
using namespace std;

const int mod = 1e9;
int n,f[100005],lstf[100005],fact[100005];
vector<int> e[100005];
long long ans;
void dfs(int now,int fath,int root){
	f[now] = 1;
	int sz = e[now].size();
	for (int i = 0;i < sz;i++){
		int son = e[now][i];
		if (son == fath) continue;
		dfs(son,now,root);
		f[now] = 1ll * f[now] * f[son] % mod;
	}
	if (now == root){
		lstf[now] = 1ll * f[now] * fact[sz-1] % mod;
		f[now] = 1ll * f[now] * fact[sz] % mod;
    //这里顺序颠倒就会先计算f,然后会乘上两遍fact[sz-1]
	}
	else lstf[now] = f[now] = 1ll * f[now] * fact[sz-1] % mod;//因为lstf本来就是计算fact[sz] / sz,所以非一号节点的节点lstf=f
}
void dfs1(int now,int fath){
	int sz = e[now].size();
	//ans = (ans + (long long)f[now]) % mod;
  //我偷偷把计算ans的代码给删了,不许复制
	for (int i = 0;i < sz;i++){
		int son = e[now][i];
		if (son == fath) continue;
		f[son] = 1ll * lstf[now] * e[son].size() % mod;
		lstf[son] = lstf[now];
		dfs1(son,now);
	}
}//这个就是用来计算答案

int main(){
	scanf("%d",&n);
	fact[0] = fact[1] = 1;
	for (int i = 1;i <= 100000;i++) fact[i] = 1ll * fact[i-1] * i % mod;//预制
	for (int i = 1;i < n;i++){
		int u,v;scanf("%d%d",&u,&v);
		e[u].push_back(v);e[v].push_back(u);
	}
	dfs(1,-1,1);
	dfs1(1,-1);
	printf("%d",ans);
	return 就是不让你复制;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...