loj#6496. 「雅礼集训 2018 Day1」仙人掌 圆方树+FFT

54 篇文章 0 订阅
28 篇文章 0 订阅

loj6496. 「雅礼集训 2018 Day1」仙人掌

题目传送门

分析

首先考虑树的情况。用 f [ u ] [ 0 / 1 ] f[u][0/1] f[u][0/1]表示当前子树根的度数 ≤ a i , &lt; a i \le a_i, &lt;a_i ai,<ai的答案。
f [ u ] [ 0 ] = ∏ k 1 + k 2 + ⋯ k n ≤ a i f [ v i ] [ 1 − k i ] f[u][0]=\prod_{k_1+k_2+\cdots k_n\le a_i}f[v_i][1-k_i] f[u][0]=k1+k2+knaif[vi][1ki]
f [ u ] [ 1 ] = ∏ k 1 + k 2 + ⋯ k n &lt; a i f [ v i ] [ 1 − k i ] f[u][1]=\prod_{k_1+k_2+\cdots k_n&lt;a_i}f[v_i][1-k_i] f[u][1]=k1+k2+kn<aif[vi][1ki]
这个东西生产函数 + N T T +NTT +NTT一下就是一个 l o g log log
仙人掌的话把环单独拉出来搞。
建圆方树,把信息放在方点上维护。由于是一个环的形式,所以某个方点可能对父亲圆点的度数最多产生 2 2 2的贡献,于是用 f [ u ] [ 0 / 1 / 2 ] f[u][0/1/2] f[u][0/1/2]。来维护。
这个时候考虑环上的转移,用 g [ i ] [ 0 / 1 ] g[i][0/1] g[i][0/1]表示环上搞到第 i i i个节点,其中 ( i , i + 1 ) (i,i+1) (i,i+1)这条边是否对 i + 1 i+1 i+1有贡献的方案数。
方程
g [ i + 1 ] [ 0 ] = g [ i ] [ 0 ] ⋅ f [ v ] [ 1 ] + g [ i ] [ 1 ] ⋅ f [ v ] [ 2 ] g[i+1][0]=g[i][0]\cdot f[v][1]+g[i][1] \cdot f[v][2] g[i+1][0]=g[i][0]f[v][1]+g[i][1]f[v][2]
g [ i + 1 ] [ 1 ] = g [ i ] [ 0 ] ⋅ f [ v ] [ 0 ] + g [ i ] [ 1 ] ⋅ f [ v ] [ 1 ] g[i+1][1]=g[i][0]\cdot f[v][0]+g[i][1] \cdot f[v][1] g[i+1][1]=g[i][0]f[v][0]+g[i][1]f[v][1]
考虑当前点的度数即可。
这样的话多处理一下度数 &lt; a i − 1 &lt;a_i-1 <ai1的答案即可。
树上的 F F T FFT FFT种类遇到的也就两种,一种套路是点分治 + F F T +FFT +FFT,一般考虑过根路径和子树转移,另一种就是关于儿子的转移,用边来分析复杂度。本题就是后者的典型。

代码

#include<bits/stdc++.h>
const int Nt = 524288, N = 2e5 + 10, P = 998244353;
int ri() {
	char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
struct Edge {
	int pr[N], to[N << 1], nx[N << 1], tp;
	Edge() {tp = 1;}
	void add(int u, int v) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp;}
	void adds(int u, int v) {add(u, v); add(v, u);}
}G, T;
int g[N][2], f[N][3], dfn[N], low[N], st[N], a[N], R[Nt], w[Nt], A[Nt], B[Nt], L, IvL, tp, tm, n, m, tot;
int Pow(int x, int k) {
	int r = 1;
	for(;k; x = 1LL * x * x % P, k >>= 1)
		if(k & 1)
			r = 1LL * r * x % P;
	return r;
}
int Inv(int x) {return Pow(x, P - 2);}
int fixu(int a) {return a >= P ? a - P : a;}
int fixd(int a) {return a < 0 ? a + P : a;}
void Up(int &a, int b) {a = fixu(a + b);}
void Pre(int m) {
	int x = 0; L = 1;
	for(;(L <<= 1) < m; ++x) ;
	for(int i = 1;i < L; ++i)
		R[i] = R[i >> 1] >> 1 | (i & 1) << x;
	int wn = Pow(3, (P - 1) / L); w[0] = 1;
	for(int i = 1;i < L; ++i)
		w[i] = 1LL * w[i - 1] * wn % P;
	IvL = Inv(L);
}
void NTT(int *F) {
	for(int i = 0;i < L; ++i)
		if(i < R[i])
			std::swap(F[i], F[R[i]]);
	for(int i = 1, d = L >> 1; i < L; i <<= 1, d >>= 1)
		for(int j = 0;j < L; j += i << 1) {
			int *l = F + j, *r = F + j + i, *p = w, tp;
			for(int k = i; k--; ++l, ++r, p += d)
				tp = 1LL * *p * *r % P, *r = fixd(*l - tp), *l = fixu(*l + tp);
		}
}
typedef std::vector<int> VI;
VI p[N];
void Get(int *A, const VI a, int L) {
	for(int i = 0;i < a.size(); ++i)
		A[i] = a[i];
	for(int i = a.size();i < L; ++i)
		A[i] = 0;
}
VI operator * (VI a, VI b) {
	int m = a.size() + b.size() - 1; 
	VI c; Pre(m);
	Get(A, a, L); Get(B, b, L);
	NTT(A); NTT(B);
	for(int i = 0;i < L; ++i)
		A[i] = 1LL * A[i] * B[i] % P;
	NTT(A);
	for(int i = 0;i < m; ++i)
		c.push_back(1LL * A[L - i & L - 1] * IvL % P);
	return c;
}
void Tarjan(int u, int p) {
	dfn[u] = low[u] = ++tm; st[++tp] = u;
	for(int i = G.pr[u], v; i;i = G.nx[i])
		if(i ^ p ^ 1) {
			if(!dfn[v = G.to[i]]) {
				Tarjan(v, i);
				low[u] = std::min(low[u], low[v]);
				if(dfn[u] == low[v])
					for(T.add(u, ++tot);st[tp + 1] != v;)
						T.add(tot, st[tp--]);
				else if(low[v] > dfn[u])
					T.add(u, v), --tp;
			}
			else low[u] = std::min(low[u], dfn[v]);
		}
}
VI Prod(int L, int R) {
	if(L == R) return p[L]; int m = L + R >> 1;
	return Prod(L, m) * Prod(m + 1, R);
}
void Dfs(int u) {
	for(int i = T.pr[u]; i; i = T.nx[i])
		Dfs(T.to[i]);
	if(u <= n) {
		if(!T.pr[u]) {
			f[u][0] = 1;
			f[u][1] = (a[u] >= 1);
			f[u][2] = (a[u] >= 2);
			return ;
		}
		tp = 0;
		for(int i = T.pr[u], v; i; i = T.nx[i]) {
			p[++tp].clear(); v = T.to[i];
			if(v > n) p[tp].push_back(f[v][2]);
			p[tp].push_back(f[v][1]);
			p[tp].push_back(f[v][0]);
		}
		VI c = Prod(1, tp);
		Get(A, c, a[u] + 1);
		for(int i = 1;i <= a[u]; ++i)
			Up(A[i], A[i - 1]);
		f[u][0] = A[a[u]];
		if(a[u] >= 1) f[u][1] = A[a[u] - 1];
		if(a[u] >= 2) f[u][2] = A[a[u] - 2];
	}
	else {
		for(int fi = 0; fi <= 1; ++fi) {
			g[0][fi] = 1; g[0][!fi] = 0; int j = 0;
			for(int i = T.pr[u], v; i; i = T.nx[i], ++j) {
				v = T.to[i];
				g[j + 1][0] = (1LL * g[j][0] * f[v][1] + 1LL * g[j][1] * f[v][2]) % P;
				g[j + 1][1] = (1LL * g[j][0] * f[v][0] + 1LL * g[j][1] * f[v][1]) % P;
			}
			Up(f[u][fi + 1], g[j][0]);
			Up(f[u][fi], g[j][1]);
		}
	}
}
int main() {
	tot = n = ri(); m = ri();
	for(int i = 1;i <= m; ++i)
		G.adds(ri(), ri());
	for(int i = 1;i <= n; ++i)
		a[i] = ri();
	Tarjan(1, 0);
	Dfs(1);
	printf("%d\n", f[1][0]);
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值