• 题意:在一棵树上dfs,求前序遍历和后序遍历的最长公共子序列,及其方案数(当然有多种dfs序,每种都有多种公共子序列方案)
  • 思路:

我是sb.

1.考场上想的是子段。
2.思维不够灵活,考后做题思考的时候没有从子段转化成子序列。认定了叶子就不会认可其它的可能(还是老毛病了)
3.特判的时候思考不够仔细全面

然后有两种做法第一种是换根dp。
当然还有一种简单的方法
1.找到叶子,往上dfs,点的度数不能超过2。其中一叶子往上找到的叶子暂且定义为叶链。
2.定义公共答案res为每个叶链长度的乘积,再乘上每个点度数减一的阶乘。(阶乘求dfs序方案数,叶链求每个dfs序每个叶链取不同值的方案数)
3.然后du大于2的中间节点为根,输出该点乘上它的du,补全阶乘
4.其余叶链上的节点为根要除去它所在叶链的长度,乘上它为根分割后的新长度。(ps.这里逆元先O(n)先行处理,这道题真的卡常)
!!特判:1.n=1; 2.链(这个简单,但我wa了5次)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
ll mod=998244353,inv[N],g[N],f[N],jc[N],bl[N],sz[N],gp;
int mx,l[N],du[N],nxt[N],to[N],head[N],ecnt;
void add_edge(int u,int v) {nxt[++ecnt]=head[u];to[ecnt]=v;head[u]=ecnt;}
void dfs(int u,int fa) {
	bl[u]=gp;
	for(int i=head[u];i;i=nxt[i]) {
		int v=to[i];
		if(v==fa||du[v]>2) continue;
		l[v]=l[u]+1;
		mx=max(mx,l[v]);
		dfs(v,u);
	}
}
int main() {
	bool flag=0;
	int n;
	scanf("%d",&n);
	if(n==1) {printf("1 1");return 0;}
	for(int i=1;i<n;i++) {
		int u,v;
		scanf("%d%d",&u,&v);
		du[u]++,du[v]++;
		if(du[u]>2||du[v]>2) flag=1;
		add_edge(u,v),add_edge(v,u);
	}
	ll res=1;
	jc[0]=1;
	for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i%mod;
	for(int i=1;i<=n;i++) {
		if(du[i]==1&&!l[i]) {
			gp++;
			mx=l[i]=1;
			dfs(i,0);
			sz[gp]=mx;
			res=res*mx%mod;
		}
	}
	if(!flag) {
		for(int i=1;i<=n;i++) {
			if(du[i]==1) {
				g[i]=1; f[i]=n;
			}
			else {
				g[i]=2; f[i]=1ll*(n-l[i])*(l[i]-1)%mod*2%mod;
			}
			printf("%lld %lld\n",g[i],f[i]%mod);
		}
		return 0;
	}
	inv[1]=1;
	for(int i=2;i<=n;i++) {
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	}
	for(int i=1;i<=n;i++) res=res*jc[du[i]-1]%mod;
	for(int i=1;i<=n;i++) {
		if(!l[i]) f[i]=res*du[i]%mod,g[i]=gp;
		else {
			g[i]=gp-(du[i]==1);
			f[i]=1ll*res*inv[sz[bl[i]]]%mod*max(l[i]-1,1)%mod*du[i]%mod;
		}
		printf("%lld %lld\n",g[i],f[i]%mod);
	}
	return 0;
}