首页 > 其他分享 >P9669 [ICPC2022 Jinan R] DFS Order 2 题解

P9669 [ICPC2022 Jinan R] DFS Order 2 题解

时间:2023-10-25 18:55:08浏览次数:43  
标签:ICPC2022 方案 int 题解 mo Jinan dfs 儿子 节点

P9669 [ICPC2022 Jinan R] DFS Order 2 题解

简要题意

给定一棵 \(n\) 个节点的树,根节点是 \(1\) 。

从根节点开始深度优先搜索这一棵树,dfs序是在搜索过程中访问节点的顺序。

对于每一个节点 \(v\) ,你要给出有多少种不同的dfs序,使得 \(v\) 出现在第 \(j\) 个位置。

答案对 \(998244353\) 取模。

解法说明

首先我们考虑一下总共有多少种dfs序。

记 \(h[x]\) 为只考虑 \(x\) 的子树内的节点所形成的dfs序的方案数。

假设 \(x\) 有 \(m\) 个儿子,那么他选第一个儿子的时候有 \(m\) 种选法,第二个儿子有 \(m - 1\) 种选法,同理第 \(i\) 个儿子有 \(m - i + 1\) 种选法,那么他儿子的选法总共有 \(m!\) 种。

然后再乘上他每个儿子的只考虑子树内节点形成dfs序的方案数。

我们可以得到 \(h[x] = jc[m]\times\prod\limits_{v \in e[x].v} h[v]\)。

其中 \(jc[i]\) 表示 \(i\) 的阶乘,\(e[x].v\) 表示 \(x\) 的儿子节点。

设 \(f[j][k]\) 表示在 \(x\) 的儿子中选了 \(j\) 个儿子, \(s\) 和为 \(k\) 的方案数。

其中 \(s[x]\) 表示 \(x\) 的子树中的总节点数。

\(f[j][k]\) 可以通过背包的方式来求出。

每一个 $j \in [1,m],k \in [s[v],n] $ 由 \(f[j - 1][k - s[v]],v \in e[x].v\) 转移而来。

因为我们把表示 \(x\) 的那一维滚掉了,所以我们需要逆序来算上面的 \(f[j][k]\)。

然后我们假设我们现在已经处理到 \(x\) 的儿子 \(v\) 这个节点。

设 \(g[k]\) 表示当前儿子节点 \(v\) 排在 \(x\) 后面 \(k\) 个位置的方案数, \(hx\) 为该节点除去儿子 \(v\) 的方案以及去掉选 \(m\) 个儿子的排列的方案。

我们知道 \(f[j][k]\) 是 \(x\) 的儿子中的总方案数,为了求出 \(v\) 节点排在父亲后的方案,我们需要求出除去 \(v\) 的方案数。

可以通过回退背包的做法,把 \(v\) 的贡献减掉。

对于每个状态 \(f[j][k]\) 我们把 \(f[j - 1][k - s[v]]\) 的贡献给减掉,上面加入贡献我们是逆序做的,这里需要正序来减。

总的方案每一次计算新的 \(v\) 都要使用,所以在这次的关于 \(v\) 的计算完成后,要把这里减掉的贡献重新加回去。

对于选择的节点数量 \(k \in [1,s[x]]\) 可以由不同的儿子节点的选择方式组成,那么 \(g[x]\) 可以从 \(f[j][k]\) 转移过来。

对于已选的 \(j\) 个儿子,有 \(j!\) 种选择,剩下的 \(m - j - 1\) 个儿子,有 \((m - j - 1)!\) 种选择。

然后再乘上 \(hx\) 就可以得到 \(g[x]\)。

这里的 \(hx = h[x] / h[v] / m!\) 是去除 \(v\) 的子树中,\(x\) 的子树中,其他节点的方案数。

现在我们求出了儿子节点排在父亲节点后 \(k\) 个位置的方案数了,那么我们便可以推出他在整棵树中dfs序的情况了。

设 \(dp[x][k]\) 表示 \(x\) 在总的dfs序中排在位置 \(k\) 的方案数(不计 \(x\) 子树内部节点方案数)。

每个儿子节点的位置都可以从他父亲的位置往后推出来,即 \(dp[v][j + k] = \sum\limits_{j\in[1,n],k\in[1,s[x]]} dp[x][j] * g[k]\)

然后 \(dp[i][j] * h[i]\) 即为题目要求的最终的方案数。

我们对于每一个节点 \(x\) 都要求一个 \(f[j][k]\),每个 \(f[j][k]\) 都需要遍历 \(x\) 的儿子数以及所有的叶子节点数,这里的复杂度是 \(O(n^3)\) 的。

同理其他几个状态的转移复杂度和这个也是同级的,所以总复杂度为 \(O(n^3)\)。

原本对于每个 \(x\) 的每个 \(v\) 单独求解除去 \(v\) 的方案应该是 \(O(n^4)\) 的复杂度,这里使用了回滚背包,优化成了 \(O(n^3)\) 。

具体实现

#include<bits/stdc++.h>

#define int long long

using namespace std;

int read()
{
	int s = 0,f = 1; char x = getchar();
	while(x < '0' || '9' < x) f = (x == '-') ? -1 : 1 , x = getchar();
	while('0' <= x && x <= '9') s = s * 10 + x - '0' , x = getchar();
	return s * f;
}

const int N = 510;
const int mo = 998244353;

int n;
int s[N],ft[N];
int h[N],son[N];
int f[N][N];
int ans[N][N],dp[N][N];
int iv[N],fc[N];

vector <int> e[N];

int fpow(int x,int nn)
{
	int res = 1;
	while(nn)
	{
		if (nn & 1) res = (res * x) % mo;
		x = (x * x) % mo;
		nn >>= 1;
	}
	return res;
}

int inv(int x)//求逆元
{
	if (x <= n) return (iv[x] * fc[x - 1]) % mo;
	return fpow(x,mo - 2);
}

void dfs(int x,int fa)
{
	ft[x] = fa;
	s[x] = 1;
	h[x] = 1;
	for (int i = 0,v;i < e[x].size();i ++)
	{
		v = e[x][i];
		if (v == fa) continue;
		son[x] ++;
		dfs(v,x);
		h[x] = (h[x] * h[v]) % mo;//在x的子树内的方案数
		s[x] += s[v];
	}
	h[x] = (h[x] * fc[son[x]]) % mo;
	return;
}

void dfs1(int x)
{
	int m = son[x];
	vector<vector<int> > f(m + 1,vector<int>(s[x] + 1,0));
	f[0][0] = 1;//不选儿子一种方案
	for (int i = 0,v;i < e[x].size();i ++)
	{
		v = e[x][i];
		if (v == ft[x]) continue;
		for (int j = m;j >= 1;j --)
		{
			for (int k = s[x];k >= s[v];k --)
			{
				f[j][k] = (f[j][k] + f[j - 1][k - s[v]]) % mo;//选v这个儿子
			}
		}
	}
	
	for (int i = 0,v;i < e[x].size();i ++)
	{
		v = e[x][i];
		if (v == ft[x]) continue;
		
		int hx = (h[x] * inv(h[v]) % mo * iv[m]) % mo;//去掉儿子中v的方案数以及去掉 选m个儿子的排列的方案

		for (int j = 1;j <= m;j ++)
			for (int k = s[v];k <= s[x];k ++)
				f[j][k] = ((f[j][k] - f[j - 1][k - s[v]]) % mo + mo) % mo;//去掉儿子v的方案数
		
		vector <int> g(n + 1,0);//g[k]表示在x后k个位置的方案数
		
		for (int j = 0;j < m;j ++)
			for (int k = 0;k < s[x];k ++)
				g[k + 1] = (g[k + 1] + f[j][k] * hx % mo * fc[j] % mo * fc[m - j - 1]) % mo;

		for (int j = 1;j <= n;j ++)
			for (int k = 1;k <= s[x];k ++)
				dp[v][j + k] = (dp[v][j + k] + dp[x][j] * g[k]) % mo;
		
		for (int j = m;j >= 1;j --)
			for (int k = s[x];k >= s[v];k --)
				f[j][k] = (f[j][k] + f[j - 1][k - s[v]]) % mo;
		
		dfs1(v);
	}
	return;
}

signed main()
{
	n = read();
	
	fc[0] = 1;
	for (int i = 1;i <= n + 1;i ++) fc[i] = (fc[i - 1] * i) % mo;
	iv[n + 1] = fpow(fc[n + 1],mo - 2);
	for (int i = n;i >= 0;i --) iv[i] = (iv[i + 1] * (i + 1)) % mo;//求阶乘逆元
	
	for (int i = 1,u,v;i < n;i ++)
	{
		u = read() , v = read();
		e[u].push_back(v) , e[v].push_back(u);
	}
	
	dfs(1,0);
	
	dp[1][1] = 1;
	
	dfs1(1);
	
	for (int i = 1;i <= n;i ++)
	{
		for (int j = 1;j <= n;j ++)
		{
			ans[i][j] = (dp[i][j] * h[i]) % mo;
			cout << ans[i][j] << ' ';
		}
		puts("");
	}
	return 0;
}

/*

5
1 2
1 3
3 4
3 5



*/

后话

这篇题解主要是参考了 2022 International Collegiate Programming Contest, Jinan Site C. DFS Order 2(树形dp+回滚背包)2022 ICPC 济南站 C (回退背包) 还有 P9669 [ICPC2022 Jinan R] DFS Order 2 三位大佬的题解。

标签:ICPC2022,方案,int,题解,mo,Jinan,dfs,儿子,节点
From: https://www.cnblogs.com/9981day/p/17787907.html

相关文章

  • P9769 HUSTFC 2023 简单的加法乘法计算题 题解
    动态规划#单调队列Question给出一个\(x=0\)通过一些操作把\(x\)变成\(y\)。有两个集合\(A,B\)。\(A\)包含了\(n\)个元素,分别是\(1-n\)的所有正整数,集合\(B\)给出\(m\)个元素,可以进行一下函数选择\(A\)中的一个元素\(a\),令\(x\)加上\(a\)选择\(B\)......
  • CF1555B题解
    分析放桌子有两种放法,一种是上下放,一种是左右放,分别计算最小值取\(\min\)即可。注意到原题使用的是平面直角坐标系,于是将原图顺时针旋转\(90^{\circ}\),将下标表示方式与代码中下标的表示方式统一,相应的,左下角和右上角也变成了左上角和右下角。代码#include<iostream......
  • Kettle链接SqlServer+Jdk8 问题解决
     这两天要弄个ldap对接,客户端server2016,数据库那边winserver2008,数据库也是2008最开是链接出现类似这样的,更换了链接mssql的Jar版本,从12换到了6的老版本,没用。  后来更改网上提示的  C:\ProgramFiles\Java\jre-1.8\lib\security\java.security文件jdk.tls.......
  • B1024 题解
    本着10月杂题题解只记重量级的原则,再加上这个系列好久没更新了,搞一发。原题链接发挥还可以的一场,至少比csp-s发挥的好。T1智慧概率题,考场差点出来,30pts。T2简单计数题,之前几场都卡T2,终于出来一次,100pts。T3简单数据结构题,打的30pts暴力,但是有50pts。T4智慧......
  • CF1572F Stations 题解-Segment Tree Beats
    20231025CF1572FStations题解-SegmentTreeBeats吉司机线段树好题!!!CF3400。传送门Statement有\(n\)个广播站,第\(i\)个广播站高度为\(h_i\),范围为\(w_i\)。初始\(h_i=0,w_i=i\)。广播站\(i\)能向广播站\(j\)传递消息,当且仅当\(i\lej\lew_i\),且\(h_i>\max\lim......
  • 恨7不成妻题解
    恨7不成妻题解分析数位\(DP\)考虑题目中的两个条件,每一位不等于\(7\)直接枚举时把\(7\)排除,其他两种情况直接放在状态里。因为题目要求平方和,我们考虑每次加上一位(设加入的是第\(i\)位)时会发生什么设原平方和为\[\sum_{k=1}^ta_k^2\]加入一位\(p\)之后每个数......
  • P9771 HUSTFC 2023 排列排序问题 题解
    Question给出一个\(N\)个元素的排序\(a\),我们可以对排列进行一些操作将这个排列切割成若干个序列将其中一些序列翻转将这些序列连接起来得到一个新的排列需要让最后的排列有序Solution这个题的描述有点小问题理解应该是切一次,然后再反转合并,不可能会先合并再切再反转......
  • P9744 「KDOI-06-S」消除序列 题解
    @目录DesciptionSolutionCodeDesciption给定一个长度为\(i\)的序列\(v_1,v_2,\dots,v_n\),初始时所有元素的值都为\(1\)。对于下标\(i\)有\(3\)种操作:将\(v_1,v_2,\dots,v_i\)的值变为\(0\),费用是\(a_i\)。将\(v_i\)的值变为\(0\),费用是\(b_i\)。将\(v_i\)......
  • Meta Hacker Cup 2023 Round 1 题解
    ProblemA:HereComesSantaClaus给一个数列,要求分成若干组,要求每组至少2个数,使得所有组中位数的最大值与最小值之差尽量大,求这个值。#include<bits/stdc++.h>usingnamespacestd;#defineFor(i,n)for(inti=1;i<=n;i++)#defineFork(i,k,n)for(inti=k;i<=n;i++)#define......
  • pytest运行警告问题解决:DeprecationWarning: pkg_resources is deprecated as an API
    前言最近在运行pytest的时候,经常出现这个警告DeprecationWarning:pkg_resourcesisdeprecatedasanAPISeehttps://setuptools.pypa.io/en/latest/pkg_resources.htmlfrompkg_resourcesimportiter_entry_points从警告上看是方法被弃用,肯定是因为新版弃用了旧版的语法。遇......