LOJ#2473「九省联考2018」秘密袭击


Address

LOJ#2473 洛谷P4365


Solution

Part 1

  • 题意即为求所有连通块的第 K K 大值之和,对 64123 64123 取模。
  • 直接求不太好做,考虑对原问题进行一些转化:
    a n s = S T k t h   o f   S (1) ans = \sum \limits_{S \in T} kth\ of\ S \tag{1} = i = 1 W i S T [ k t h   o f   S = i ] (2) = \sum \limits_{i = 1}^{W} i \sum \limits_{S \in T} [kth\ of\ S =i] \tag{2} = i = 1 W S T [ k t h   o f   S i ] (3) = \sum \limits_{i = 1}^{W} \sum \limits_{S \in T}[kth\ of\ S \ge i] \tag{3} = i = 1 W S T [ c n t S [ i ] K ] (4) = \sum \limits_{i = 1}^{W} \sum \limits_{S \in T}[cnt_S[i] \ge K] \tag{4}
  • T T 表示树上的所有连通块集合。
  • ( 2 ) (2) 步到第 ( 3 ) (3) 步是一个常见的转化:对于布尔表达式 [ k t h   o f   S i ] [kth\ of\ S \ge i] ,每个布尔表达式 [ k t h   o f   S = i ] [kth\ of\ S = i] 恰好被算了 i i 次。
  • ( 4 ) (4) 步中的 c n t S [ i ] cnt_S[i] 表示连通块 S S 中权值 i \ge i 的结点个数。
  • 据此我们可以设 f [ u ] [ i ] [ j ] f[u][i][j] 表示在以 u u 为根的子树内包含点 u u 且恰好有 j j 个权值 i \ge i 的结点的连通块个数。
  • 特别地,我们定义选取的连通块大小为空也是一种方案。
  • 则最后的答案为: u = 1 n i = 1 W j = K n f [ u ] [ i ] [ j ] \sum \limits_{u = 1}^{n} \sum \limits_{i = 1}^{W} \sum \limits_{j = K}^{n}f[u][i][j]
  • 转移显然为: f [ u ] [ i ] [ j ] = v s o n u f [ v ] [ i ] [ j v ] ( d u i , j v 0 j v = j 1 ) f[u][i][j] = \prod \limits_{v \in son_u} f[v][i][j_v](d_u \ge i, \sum \limits_{j_v \ge 0} j_v = j - 1) f [ u ] [ i ] [ j ] = v s o n u f [ v ] [ i ] [ j v ] ( d u < i , j v 0 j v = j ) f[u][i][j] = \prod \limits_{v \in son_u} f[v][i][j_v](d_u < i, \sum \limits_{j_v \ge 0} j_v = j)
  • 初值 f [ u ] [ i ] [   [ d u i ]   ] = 1 f[u][i][\ [d_u \ge i]\ ] = 1 ,每次枚举子节点 v v j v j_v 进行合并。
  • u u 为根的子树转移结束后,因为连通块大小为空也算一种方案,我们要令 f [ u ] [ i ] [ 0 ] f[u][i][0] 加一。
  • 时间复杂度 O ( n 2 W ) O(n^2 W) ,实现时可以把 i i 这一维提到外面枚举,就不需要在状态中记录 i i 只要有足够优秀的常数就能通过所有数据

Part 2

  • 考虑复杂度正确的做法,即如何优化 DP 的转移。
  • 容易发现转移是一个卷积的形式,我们设 F [ u ] [ i ] F[u][i] 表示 f [ u ] [ i ] [ j ] f[u][i][j] 的生成函数,即: F [ u ] [ i ] = j = 0 n f [ u ] [ i ] [ j ] x j F[u][i] = \sum \limits_{j = 0}^{n} f[u][i][j] x^j
  • 我们再另外设 G [ u ] [ i ] = v s u b t r e e u F [ v ] [ i ] G[u][i] = \sum \limits_{v \in subtree_u} F[v][i] ,答案就变为 G [ r o o t ] [ i ] G[root][i] ( r o o t root 为我们选定的树根) 的后 n K + 1 n - K + 1 项的系数之和。
  • 转移:
    F [ u ] [ i ] = x v s o n u F [ v ] [ i ] ( d u i ) F[u][i] = x \prod \limits_{v \in son_u} F[v][i](d_u \ge i) F [ u ] [ i ] = v s o n u F [ v ] [ i ] ( d u < i ) F[u][i] = \prod \limits_{v \in son_u} F[v][i](d_u < i) G [ u ] [ i ] = F [ u ] [ i ] + v s o n u G [ v ] [ i ] G[u][i] = F[u][i] + \sum \limits_{v \in son_u} G[v][i]
  • 同样地,以 u u 为根的子树转移结束后,我们要令 F [ u ] [ i ] F[u][i] 的常数项加一。
  • 考虑如果我们直接维护这些多项式,不仅复杂度没有保证、常数极大,模数也不是 NTT 模数,无法较为方便地进行卷积,显然是行不通的。
  • 换个思路考虑,多项式点值的加法和乘法运算相当方便。我们把所有 G [ r o o t ] [ i ] G[root][i] 相加合并为一个多项式。现在我们只要求出这个多项式的 n + 1 n + 1 个点值,就可以通过拉格朗日插值公式 O ( n 2 ) O(n^2) 还原这个多项式的各项系数,从而得到答案。
  • 我们在最外层枚举 x = 1 n + 1 x = 1 \to n +1 ,那么点值关于 i i 这一维的初值设定就相当于线段树上的区间修改,转移则可以看做多棵线段树的合并。
  • 考虑直接在线段树上打标记维护 F , G F,G 的点值 ( f , g ) (f, g)
  • 具体地,我们定义一种对于 ( f , g ) (f, g) 的变换 ( a , b , c , d ) (a,b,c,d) 表示把 ( f , g ) (f,g) 变为 ( a × f + b , c × f + d + g ) (a \times f + b, c \times f + d + g) ,直接在线段树上维护这样的标记。
  • 标记的合并如下: ( a 1 , b 1 , c 1 , d 1 ) + ( a 2 , b 2 , c 2 , d 2 ) (a_1, b_1, c_1, d_1) + (a_2, b_2, c_2, d_2) = ( a 1 × a 2 , a 2 × b 1 + b 2 , a 1 × c 2 + c 1 , b 1 × c 2 + d 1 + d 2 ) = (a_1 \times a_2,a_2 \times b_1 + b_2, a_1 \times c_2 + c_1,b_1 \times c_2 + d_1 + d_2)
  • 则对于每个结点 u u 相当于一开始在 [ 1 , d u ] [1, d_u] 打上标记 ( 1 , x , 0 , 0 ) (1, x, 0, 0) ,在 [ d u + 1 , W ] [d_u + 1, W] 打上标记 ( 1 , 1 , 0 , 0 ) (1, 1, 0, 0) ,转移完后再打上标记 ( 1 , 1 , 1 , 0 ) (1, 1, 1, 0)
  • 注意动态开点的线段树在标记下传时也要新建结点,并且如果合并时其中一个结点没有左右子节点,我们不能直接标记下传(那样新建的结点数就不止与两棵线段树公共的结点数有关,时间复杂度不正确)。
  • 因为 ( f , g ) (f, g) 初值都为 0,我们可以直接把这个结点的 b b 乘到另一个结点的 a , b a,b 上,把这个结点的 d d 加到另一个结点的 d d 上,就不用再继续合并下去了。这其实也是我们需要维护 a , c a,c 的原因。
  • 最终叶子结点的 ( f , g ) (f, g) 即为其标记中的 b b d d
  • 时间复杂度 O ( n 2 log W ) O(n^2 \log W) 由于常数原因效率可能还不如暴力做法。

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	char ch;
	while (ch = getchar(), !isdigit(ch));
	res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
}

using std::vector;
const int mod = 64123;
const int M = 1e6 + 5;
const int N = 2005;
int F[N], rt[N], stk[M], yy[N], d[N];
int top, T, n, K, W, fans;
vector<int> e[N];

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline int quick_pow(int x, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1)
			res = 1ll * res * x % mod;
		x = 1ll * x * x % mod;
		k >>= 1;
	}
	return res;
}

struct tag
{
	int a, b, c, d;

	tag() {}
	tag(int A, int B, int C, int D):
		a(A), b(B), c(C), d(D) {}

	inline tag operator + (const tag &x) const 
	{
		return tag(1ll * a * x.a % mod,
				   (1ll * x.a * b + x.b) % mod,
				   (1ll * a * x.c + c) % mod,
				   (1ll * b * x.c + d + x.d) % mod);
	}

	inline bool operator == (const tag &x) const 
	{
		return a == x.a && b == x.b && c == x.c && d == x.d;
	}
};
const tag One = tag(1, 0, 0, 0);

struct node
{
	tag x; 
	int lc, rc;

	#define lc(x) tr[x].lc
	#define rc(x) tr[x].rc

	inline void Clear()
	{
		x = One;
		lc = rc = 0;
	}
}tr[M];

inline int new_node()
{
	return top ? stk[top--] : ++T;
}

inline void addTag(int &s, const tag &w)
{	
	if (!s)
		tr[s = new_node()].Clear();
	tr[s].x = tr[s].x + w;
}

inline void downDate(int &s)
{
	if (!s)
		tr[s = new_node()].Clear();
	if (tr[s].x == One)
		return ;
	addTag(lc(s), tr[s].x);
	addTag(rc(s), tr[s].x);
	tr[s].x = One;
}

inline void Modify(int &s, int l, int r, int u, int v, const tag &w)
{
	if (l == u && r == v)
		return addTag(s, w);
	downDate(s);
	int mid = l + r >> 1;
	if (v <= mid)
		Modify(lc(s), l, mid, u, v, w);
	else if (u > mid)
		Modify(rc(s), mid + 1, r, u, v, w);
	else 
	{
		Modify(lc(s), l, mid, u, mid, w);
		Modify(rc(s), mid + 1, r, mid + 1, v, w);
	}
}

inline void Merge(int &s1, int s2)
{
	if (!s1 || !s2)
		return (void)(s1 += s2);
	if (!lc(s1) && !rc(s1))
		std::swap(s1, s2);
	if (!lc(s2) && !rc(s2))
	{
		tr[s1].x.a = 1ll * tr[s1].x.a * tr[s2].x.b % mod;
		tr[s1].x.b = 1ll * tr[s1].x.b * tr[s2].x.b % mod;
		add(tr[s1].x.d, tr[s2].x.d);
		stk[++top] = s2;
		return ;
	}
	downDate(s1);
	downDate(s2);

	Merge(lc(s1), lc(s2));
	Merge(rc(s1), rc(s2));
	stk[++top] = s2;
}

inline void Print(int s, int l, int r, int v)
{
	if (!s)
		return ;
	if (l == r)
		return add(yy[v], tr[s].x.d);
	downDate(s);
	int mid = l + r >> 1;
	Print(lc(s), l, mid, v);
	Print(rc(s), mid + 1, r, v);
}

inline void Lagrange()
{
	F[0] = 1;
	for (int i = 1; i <= n + 1; ++i)
	{
		for (int j = i; j >= 1; --j)
			F[j] = F[j - 1];
		F[0] = 0;
		for (int j = 1; j <= i; ++j)
			F[j - 1] = (1ll * (mod - i) * F[j] + F[j - 1]) % mod;
	}

	for (int i = 1; i <= n + 1; ++i)
	{
		int p = yy[i], q = 1;
		for (int j = 1; j <= n + 1; ++j)
			if (i != j)
				q = 1ll * q * (i - j + mod) % mod;
		p = 1ll * p * quick_pow(q, mod - 2) % mod;

		for (int j = n + 1; j >= 1; --j)
			F[j - 1] = (F[j - 1] + 1ll * i * F[j]) % mod;
		for (int j = 1; j <= n + 1; ++j)
			F[j - 1] = F[j];
		F[n + 1] = 0;

		for (int j = K; j <= n; ++j)
			fans = (fans + 1ll * p * F[j]) % mod;
		
		for (int j = n + 1; j >= 1; --j)
			F[j] = F[j - 1];
		for (int j = 1; j <= n + 1; ++j)
			F[j - 1] = (1ll * (mod - i) * F[j] + F[j - 1]) % mod;	
	}
}

inline void Dfs(int x, int Fa, int v)
{
	Modify(rt[x], 1, W, 1, d[x], tag(1, v, 0, 0));
	if (d[x] < W)
		Modify(rt[x], 1, W, d[x] + 1, W, tag(1, 1, 0, 0));
	for (int i = 0, im = e[x].size(); i < im; ++i)
	{
		int y = e[x][i];
		if (y == Fa)
			continue;
		Dfs(y, x, v);
		Merge(rt[x], rt[y]);
	}
	addTag(rt[x], tag(1, 1, 1, 0));
}

inline void solve(int v)
{
	for (int i = 1; i <= n; ++i)
		rt[i] = 0;
	T = top = 0;

	Dfs(1, 0, v);
	Print(rt[1], 1, W, v);
}

int main()
{	
	read(n); read(K); read(W);
	for (int i = 1; i <= n; ++i)
		read(d[i]);
	for (int i = 1, x, y; i < n; ++i)
	{
		read(x); read(y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	
	for (int i = 1; i <= n + 1; ++i)
		solve(i);	
	Lagrange();
	printf("%d\n", fans);
}

发布了104 篇原创文章 · 获赞 125 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/bzjr_Log_x/article/details/99684593