「LOJ2462」「2018 集训队互测 Day 1」完美的集合

Description

链接

Solution

首先对于每一种选取 K K K个集合的方案,合法的测试点一定是个联通块。

所以可以容斥求出方案数,即 u u u可行的减去 f a u fa_u fau u u u都可行的。

一次树上背包直接做是 O ( n m 2 ) O(nm^2) O(nm2) 的,但是在 d f s dfs dfs 序上倒序 D P DP DP 可以做到 O ( n m ) O(nm) O(nm),具体实现可以看代码。

考虑如何计算那个组合数。可以发现

n ! = ∏ i ∈ [ 1 , n ] , i m o d    5 ≠ 0 i × ∏ i ∈ [ 1 , ⌊ n 5 ⌋ ] , i m o d    5 ≠ 0 i × 5 ⌊ n 5 ⌋ × … n!=\prod_{i \in [1,n],i \mod 5 \neq 0}i \times \prod_{i \in [1,\lfloor \frac {n} {5} \rfloor],i \mod 5 \neq 0}i \times5^{\lfloor \frac {n} {5} \rfloor} \times \dots n!=i[1,n],imod5=0i×i[1,5n],imod5=0i×55n×

这就意味着只要计算 1 − n 1-n 1n中非 5 5 5倍数的乘积和因子 5 5 5的个数就可以快速求出我们想要的答案。

设多项式 f n ( x ) ∏ i ∈ [ 1 , n ] , i   m o d   5 ≠ 0 ( x + i ) f_n(x)\prod_{i \in [1,n],i\ mod\ 5 \neq 0}(x+i) fn(x)i[1,n],i mod 5=0(x+i),要求的是 f n ( 0 ) f_n(0) fn(0)

k k k为满足 10 k 10k 10k不超过 n n n的最大整数,求 f 10 k ( 0 ) f_{10k}(0) f10k(0)后剩下的暴力乘上去。 f 10 k ( 0 ) = f 5 k ( 0 ) × f 5 k ( 5 k ) f_{10k}(0)=f_{5k}(0)\times f_{5k}(5k) f10k(0)=f5k(0)×f5k(5k),注意当 f 5 k ( 5 k ) f_{5k}(5k) f5k(5k)展开到第 23 23 23项后贡献为 0 0 0,所以只保留前 23 23 23项即可。

复杂度 O ( n 2 m + 2 3 2 n l o g k ) O(n^2m+23^2nlogk) O(n2m+232nlogk)

#include 
using namespace std;

typedef long long lint;
typedef pair<lint, lint> pll;

const lint mod = 11920928955078125, phi = mod / 5 * 4;
const int maxn = 65, maxm = 10005, maxd = 25;

int n, m, k, w[maxn], v[maxn];
lint Max, lim, ans;

int dis[maxn][maxn], fa[maxn];

inline lint gi()
{
	char c = getchar();
	while (c < '0' || c > '9') c = getchar();
	lint sum = 0;
	while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

inline lint inc(lint a, lint b) {return a + b >= mod ? a + b - mod : a + b;}
inline lint mul(lint a, lint b) {return (__int128)a * b % mod;}

inline lint fpow(lint x, lint k)
{
	lint res = 1;
	while (k) {
		if (k & 1) res = mul(res, x);
		x = mul(x, x); k >>= 1;
	}
	return res;
}

inline lint inv(lint x) {return fpow(x, phi - 1);}

namespace Binom
{

	lint C[maxd][maxd], pw[maxd];
	
	struct poly
	{
		
		lint a[maxd];
		poly() {memset(a, 0, sizeof(a));}
		poly(lint d) {
			memset(a, 0, sizeof(a));
			a[1] = 1;
			a[0] = d;
		}
		
		friend inline poly operator * (const poly &a, const poly &b) {
			poly c;
			for (int i = 0; i <= 23; ++i)
				if (b.a[i])
					for (int j = 0; i + j <= 23; ++j)
						c.a[i + j] = inc(c.a[i + j], mul(a.a[j], b.a[i]));
			return c;
		}
		
		void extend(lint k)
		{
			pw[0] = 1;
			for (int i = 1; i <= 23; ++i) pw[i] = mul(pw[i - 1], k);
			for (int i = 0; i <= 23; ++i) {
				lint res = 0;
				for (int j = i; j <= 23; ++j) res = inc(res, mul(a[j], mul(C[j][i], pw[j - i])));
				a[i] = res;
			}
		}
		
	} pre[10005];

	void prepare()
	{
		C[0][0] = 1;
		for (int i = 1; i <= 23; ++i) {
			C[i][0] = 1;
			for (int j = 1; j <= 23; ++j)
				C[i][j] = inc(C[i - 1][j - 1], C[i - 1][j]);
		}
		pre[0].a[0] = 1;
		for (int i = 1; i <= 10000; ++i)
			if (i % 5) pre[i] = pre[i - 1] * poly(i);
			else pre[i] = pre[i - 1];
	}

	inline poly getpoly(lint n)
	{
		if (n <= 10000) return pre[n];
		lint k = n / 10 * 10;
		poly t1 = getpoly(k >> 1), t2 = t1;
		t2.extend(k >> 1);
		t2 = t1 * t2;
		for (lint i = k + 1; i <= n; ++i)
			if (i % 5) t2 = t2 * poly(i);
		return t2;
	}

	inline pll calc(lint n)
	{
		poly t = getpoly(n);
		lint res = n / 5;
		if (res > 0) {
			pll t2 = calc(n / 5);
			t.a[0] = mul(t.a[0], t2.first);
			res = inc(res, t2.second);
		}
		return make_pair(t.a[0], res);
	}
	
	inline lint binom(lint n)
	{
		if (n < k) return 0;
		pll t1 = calc(n), t2 = calc(n - k), t3 = calc(k);
		t1.second -= (t2.second + t3.second);
		if (t1.second >= 23) return 0;
		t1.first = mul(t1.first, mul(fpow(5, t1.second), inv(mul(t2.first, t3.first))));
		return t1.first;
	}
	
}

namespace tree
{

	struct edge
	{
		int to, next, w;
	} e[maxn * 2];
	int h[maxn], tot;

	int valid[maxn], id[maxn], siz[maxn], cnt;
	lint f[maxn][maxm], g[maxn][maxm];
	
	inline void add(int u, int v, int w)
	{
		e[++tot] = (edge) {v, h[u], w}; h[u] = tot;
		e[++tot] = (edge) {u, h[v], w}; h[v] = tot;
	}

	void dfs_pre(int rt, int u, int f)
	{
		fa[u] = f;
		for (int i = h[u], v; v = e[i].to, i; i = e[i].next)
			if (v != f) {
				dis[rt][v] = dis[rt][u] + e[i].w; 
				dfs_pre(rt, v, u);
			}
	}
	
	void dfs(int u, int fa)
	{
		id[++cnt] = u; siz[u] = 1;
		for (int i = h[u], v; v = e[i].to, i; i = e[i].next)
			if (v != fa && valid[v]) dfs(v, u), siz[u] += siz[v];
	}

	pll calc(int x, int y)
	{
		cnt = 0;
		dfs(x, 0);

		for (int i = 0; i <= m; ++i) f[cnt + 1][i] = 0, g[cnt + 1][i] = 1;
		for (int u, i = cnt; i >= 1; --i) {
			u = id[i];
			for (int j = 0; j <= m; ++j) {
				if (y == u && j < w[u]) {
					f[i][j] = g[i][j] = 0;
				} else if (y == u || (j >= w[u] && f[i + siz[u]][j] < f[i + 1][j - w[u]] + v[u])) {
					f[i][j] = f[i + 1][j - w[u]] + v[u];
					g[i][j] = g[i + 1][j - w[u]];
				} else if (j < w[u] || f[i + siz[u]][j] > f[i + 1][j - w[u]] + v[u]) {
					f[i][j] = f[i + siz[u]][j];
					g[i][j] = g[i + siz[u]][j];
				} else {
					f[i][j] = f[i + siz[u]][j];
					g[i][j] = g[i + siz[u]][j] + g[i + 1][j - w[u]];
				}
			}
		}
		return make_pair(f[1][m], g[1][m]);
	}
	
}

lint solve(int a, int b)
{
	for (int i = 1; i <= n; ++i)
		if ((lint)dis[a][i] * v[i] <= Max && (lint)dis[b][i] * v[i] <= Max) tree::valid[i] = 1;
		else tree::valid[i] = 0;
	if (!tree::valid[a] || !(tree::valid[b] || (!b))) return 0;
	pll t = tree::calc(a, b);
	if (t.first != lim) return 0;
	return Binom::binom(t.second);
}

int main()
{
	n = gi(); m = gi(); k = gi(); Max = gi();

	Binom::prepare();

	for (int i = 1; i <= n; ++i) w[i] = gi();
	for (int i = 1; i <= n; ++i) v[i] = gi();
	
	for (int u, v, w, i = 1; i < n; ++i) {
		u = gi(); v = gi(); w = gi();
		tree::add(u, v, w);
	}

	for (int i = n; i >= 1; --i) tree::dfs_pre(i, i, 0);
	for (int i = 1; i <= n; ++i) tree::valid[i] = 1;
	for (int i = 1; i <= n; ++i) lim = max(lim, tree::calc(i, 0).first);

	if (!lim) return puts("0"), 0;

	for (int i = 1; i <= n; ++i)
		ans = inc(ans, solve(i, 0) - (fa[i] ? solve(i, fa[i]) : 0));

	printf("%lld\n", ans);
	
	return 0;
}

你可能感兴趣的:(文章类型——题解)