CSP2024-9

如此成绩,如何 noip?

A

题意:\(T\) 组询问,每次给出一个正整数 \(n = p^kq^k \le 10^{18}\)

求非降序列 \(\{a_{m}\}\)\(m > 1\))的数量,满足 \(\prod a_i = n\)

非降并不需要真正考虑每个数的顺序,这很不可做。

考虑 \(n\) 的每个因数在序列中的出现次数。

一个长为 \((k + 1)^2 - 2\) 的向量组 \([c_1, c_2 \cdots, c_i, \cdots]\) 可以唯一对应一个序列,这是一个双射。

转化到完全背包模型,\(f_{i, j}\) 表示乘到 \(p^iq^j\) 的方案数。枚举因子 \(p^{i}q^j\),顺序更新 \(p^{x \ge i}q^{y \ge j}\)

答案只与 \(k\) 有关,瓶颈在于如何快速找 \(k\)。(\(6^{24} > 10^{18}\),所有 \(f\) 可以预处理)

枚举 \(k\),直接调用 pow(1, 1./ k),加 0.5 规避精度问题。

void init() {
	f[0][0] = 1;
	for(int i = 0; i <= 23; ++ i) {
		for(int j = 0; j <= 23; ++ j) {
			if(i || j)
				for(int x = i; x <= 23; ++ x) {
					for(int y = j; y <= 23; ++ y) {
						f[x][y] += f[x - i][y - j];
					}
				}
		}
	}
}
void solve() {
	cin >> n;
	for(int i = 23; i >= 1; -- i) {
		auto x = powl(n, 1./ i); ll y = x + 0.5;
		if(fabsl(x - y) <= eps) {
			cout << f[i][i] - 1 << '\n';
			return;
		}
	}
}

B

题意:从时刻 \(0\) 遍历一棵树,每一时刻只能走一条边。每个点的代价为到达这个点的时刻,\(n \le 10^5\)

钦定起点(选根),肯定是一棵子树遍历完了再去其他地方。

定义 \(f(i)\) 表示走完 \(i\) 这棵子树的最小代价。

考虑每个儿子 \(j\),从 \(i\) 走到任意 \(j\) 子树内的点都会经过 \((i, j)\) 并使他的代价加 \(1\),因此 \(i\)\(j\) 的额外贡献等于 \(sz_j\)

已经遍历完的子树 \(k\)\(j\) 中每个点的额外贡献等于在 \(k\) 中经过的时间 \(2 sz_k\)

\[f(i) \gets f(j) + sz_j (1+ 2\sum_{k} sz_k) \]

\(sz_k\) 会对所有在他之后的子树产生额外贡献,这是不是意味着我们需要按照子树大小升序遍历呢?

不管怎么遍历,数对 \((j, k)\) 对答案的贡献都是 \(2sz_j\cdot sz_k\),因此 \(f(i)\) 可以写成:

\[\sum_{j} \big(f(j) + sz_j\big) + 2\sum_{j, k} sz_j \cdot sz_k \]

换根好像还挺好写的。

#include<bits/stdc++.h>
#define eb emplace_back
#define ep emplace
using namespace std;

using ll = long long;
constexpr int N = 1e5 + 5;

int n, m, rt, sz[N]; ll f[N], g[N], tmp[N], sum[N], ans = 1e18;
vector<int> G[N];

void dfs(int x, int fa) {
	f[x] = sz[x] = 0;
	for(int y : G[x]) {
		if(y == fa) continue;
		dfs(y, x);
		f[x] += sz[y] + f[y] + 2ll * sz[y] * sz[x];
		sum[x] += f[y];
		tmp[x] += 2ll * sz[y] * sz[x];
		sz[x] += sz[y];
	}
	++ sz[x];
}
void dfs1(int x, int fa) {
	ans = min(ans, g[x]);
	for(int y : G[x]) {
		if(y == fa) continue;
		g[y] = sum[y] + n - 1;
		g[y] += (g[x] - f[y] - sz[y] - 2ll * sz[y] * (n - sz[y] - 1));
		tmp[y] += 2ll * (sz[y] - 1) * (n - sz[y]);
		g[y] += tmp[y];
		dfs1(y, x);
	}
}
int main() {
	cin.tie(0)->sync_with_stdio(0);
	cin >> n;
	for(int i = 1; i <= n - 1; ++ i) {
		int u, v; cin >> u >> v;
		G[u].eb(v);
		G[v].eb(u);
	}
	dfs(1, 0);
	g[1] = f[1], dfs1(1, 0);
	cout << ans;
	return 0;
}

C

题意:\((x_1, \cdots, x_m)\)好的,当且仅当对于 \(1 < i \le m\),要么有 \(x_i = x_{i - 1} + 1\),要么存在 \(k \le j < i\) 使得 \(x_i = x_kx_j\)

序列 \((x_i)\) 的代价定义为 \(\sum w_{x_i}\)。现在给定 \(w_1 \sim w_n\),要求你对于每个 \(i \in [1, n]\) 求出包含 \(i\) 的好的序列的最小代价。

定义 \(dp(S)\) 表示钦定集合 \(S\) 中元素都出现的最小代价。

发现去掉集合最大值不会对前面元素有影响,递归为子问题,可以胡乱转移。

\(n = 3 \times 10^4\) 时状态数约等于 \(4 \times 10^5\),时间复杂度不会证。

#include<bits/stdc++.h>
#define eb emplace_back
#define ep emplace
using namespace std;

using ll = long long;

map<set<int>, ll> mp;
int n, w[30005];

ll dfs(set<int> se)  {
	if(mp.count(se)) return mp[se];
	int mx = *se.rbegin();
	if(mx == 1) return w[1];
	se.erase(mx);
	auto tmp = se;
	tmp.ep(mx - 1);
	ll ret = w[mx] + dfs(tmp);
	for(int i = 2; i <= mx / i; ++ i) {
		if(mx % i == 0) {
			tmp = se, tmp.ep(i), tmp.ep(mx / i);
			ret = min(ret, w[mx] + dfs(tmp));
		}
	}
	return se.insert(mx), mp[se] = ret;
}

int main() {
	cin.tie(0)->sync_with_stdio(0);
	cin >> n;
	for(int i = 1; i <= n; ++ i) {
		cin >> w[i];
	}
	for(int i = 1; i <= n; ++ i) {
		cout << dfs({i}) << '\n';
	}
	return 0;
}

D

神秘交互。题都没看。

posted @ 2024-08-24 16:23  Lu_xZ  阅读(46)  评论(0)    收藏  举报