长春集训 Day 6 - 模拟赛

长春集训 Day 6 - 模拟赛

80+0+0+100。期望和主席树一概不会,但学了一下期望。

T1 - 冒险者

Description

给定一个长度为 \(n\) 的数组 \(a\),每次可以进行如下几种操作中的一种:

  • 删除一个元素;
  • 使一个元素的值自增 \(1\)

特别地,当数组内所有剩余元素的和为 \(x\) 的整数倍时,可以不花费操作次数,将它们全部删除。

求将所有元素全部删除的最少操作次数。

\(1 \le n,x,a_i \le 1000\)

Reflection

有一个很朴素的 DP,二重决策状物,但是不可优化。

可以换一个思考方式,将问题转化一下,定义不同的 DP 数组,就可以优化了。

Solution

注意到数据范围很小,那么余数也很小。将所有 \(a_i\) 先对 \(x\) 取模一定不劣。

定义 \(dp_r\) 表示删除若干个数后,剩下的元素之和模 \(x\)\(r\) 时的最少操作次数。如果删除,则 \(dp'_r \leftarrow dp_r + 1\);如果保留,则 \(dp'_r \leftarrow dp_{(r-a_i)\bmod x}\)。需要两个 \(dp\) 数组来回滚动。

最后将消除剩余数字的贡献加进去,枚举余数 \(r\),则答案为 \(\min\limits_{r=0}^{x-1}\{dp_r + (x-r) \bmod x\}\)

Code

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 1005;
int n, x, a[N], ans = inf;
int dp[2][N];
signed main() {
	cin.tie(0) -> sync_with_stdio(0);
	// file(player);
	cin >> n >> x;
	for (int i = 1; i <= n; i++) {
		cin >> a[i]; a[i] %= x;
	}
	memset(dp, 0x3f, sizeof dp);
	dp[0][0] = 0;
	for (int i = 1; i <= n; i++) {
		for (int r = 0; r < x; r++) {
			dp[1][r] = min(dp[0][r] + 1, dp[0][(r - a[i] + x) % x]);
		}
		for (int r = 0; r < x; r++) {
			swap(dp[0][r], dp[1][r]);
		}
	}
	for (int r = 0; r < x; r++) {
		ans = min(ans, dp[0][r] + (x - r) % x);
	}
	cout << ans;
	return 0;
}

T2 - 客服兼职

Description

Saki 要连续打工 \(n\) 天,第 \(i\) 天被评优的概率为 \(p_i\)(以最简分数形式给出),评差的概率为 \(1-p_i\)。Saki 薪资的计算方法如下:

  • 指定一个整数 \(k\)
  • 如果她某天被评优,且截至当天已连续 \(X\) 天评优,则她获得 \(X^k\) 元;
  • 如果她某天被评差,且截至当天已连续 \(X\) 天评差,则她失去 \(X^k\) 元;

Saki 初始有 \(0\) 元,且拥有的钱数可以为负,求打完 \(n\) 天工后她薪资的期望值(以分数对 \(10^9+7\) 取模的形式给出)。

\(1 \le n \le 10^5,\ 2 \le k \le 10\)

Solution

期望的重要性质(线性性):对于随机变量 \(X,Y\),一定有 \(E[X+Y] = E[X] + E[Y]\)无论 \(X,Y\) 是否相互独立。

因此,设第 \(i\) 天获得的收益为 \(I_i\),尽管它们互相制约,但也一定有 \(E[\sum\limits_{i=1}^n I_i] = \sum\limits_{i=1}^n E[I_i]\)。因此,我们只需对于每个 \(i\),计算对应的 \(E[I_i]\) 即可。

对于第 \(i\) 天,设截至当前连续评优的天数为 \(W\),连续评差的天数为 \(L\),那么有

\[E[I_i] = E[W^k] - E[L^k] \]

只需求后面两个期望即可。我们先考虑 \(W\)\(L\) 同理)。

假设我们知道第 \(i\) 天连续评优 \(W\) 天时 \(E[W^k]\) 的值,第 \(i+1\) 天如果继续评优,那么 \(E[(W+1)^k] = p_{i+1} E[W^k]\),而 \((W+1)^k\) 展开项很多,并不容易维护。

但我们知道第二类 Stirling 数可以将幂展开为组合数,具体地,有

\[W^k = \sum_{j=0}^k \begin{Bmatrix} k \\ j \end{Bmatrix} \cdot j! \cdot \binom Wj \]

由于期望是线性运算,对等式两边取期望,得

\[E[W^k] = \sum_{j=0}^k \begin{Bmatrix} k \\ j \end{Bmatrix} \cdot j! \cdot E\bigg[\binom Wj\bigg] \]

我们又熟知组合数具有一个漂亮的递推性质

\[\binom{W+1}j = \binom Wj + \binom W{j-1} \]

因此有

\[E\bigg[\binom{W+1}j\bigg] = E\bigg[\binom Wj\bigg] + E\bigg[\binom W{j-1}\bigg] \]

因此,对于 \(\forall j \in [0,k]\),只需维护 \(E\bigg[\dbinom Wj \bigg]\) 即可。记上式为 \(EW_j\),那么转移是容易的,即

\[EW'_j \leftarrow p_{i}(EW_j + EW_{j-1}) \]

边界为 \(EW_0 = 1\)

然后,对于第 \(i\) 天的答案期望,我们有 \(E_i = \sum\limits_{j=1}^k \bigg( j! \cdot \begin{Bmatrix} k \\ j \end{Bmatrix} \bigg) \cdot (EW_j - EL_j)\)

最后求 \(\sum\limits_{i=1}^n E_i\) 即可。

补充说明

  • 第二类 Stirling 数满足如下所示的递推式

    \[\begin{Bmatrix} i \\ j \end{Bmatrix} = \begin{cases} [i=0],\ &j=0\\ \begin{Bmatrix} i-1 \\ j-1 \end{Bmatrix} + j \cdot \begin{Bmatrix} i-1 \\ j \end{Bmatrix},\ &\rm otherwise \end{cases} \]

  • \(\begin{Bmatrix} i \\ j \end{Bmatrix}\) 表示将 \(i\) 个两两不同的元素划分为 \(j\) 个互不相同的非空子集的方案数,也可理解为将 \(i\) 个有标号的球放入 \(j\) 个无标号且不允许为空的盒子中的不同放置方法。

  • 求分数 \(\dfrac ab\) 在模 \(M\) 下的值可以用 a * qpow(b, M-2) % M

Code

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 1e5+5, mod = 1e9+7;
int n, K, ans;
int P[N];
int qpow(int x, int y) {
	if (y == 0) return 1;
	int tmp = qpow(x, y/2);
	if (y & 1) return tmp * tmp % mod * x % mod;
	return tmp * tmp % mod;
}
int strl[11][11], fac[11];
void init() {
	strl[0][0] = fac[0] = 1;
	for (int i = 1; i <= K; i++) {
		for (int j = 1; j <= i; j++) {
			strl[i][j] = (strl[i-1][j-1] + 1ll * j * strl[i-1][j] % mod) % mod;
		}
	}
	for (int i = 1; i <= K; i++) {
		fac[i] = 1ll * fac[i-1] * i % mod;
	}
}
int E[2][2][11]; // win/lose   cur/nxt   days
signed main() {
	cin.tie(0) -> sync_with_stdio(0);
	// file(money);
	cin >> n >> K;
	init();
	for (int i = 1; i <= n; i++) {
		int x, y; cin >> x >> y;
		P[i] = 1ll * x * qpow(y, mod-2) % mod;
	}
	E[0][0][0] = E[1][0][0] = 1;
	for (int i = 1; i <= n; i++) {
		E[0][1][0] = E[1][1][0] = 1;
		int p = P[i], q = (1-P[i]+mod) % mod;
		for (int j = 1; j <= K; j++) {
			E[0][1][j] = 1ll * p * (E[0][0][j] + E[0][0][j-1]) % mod;
			E[1][1][j] = 1ll * q * (E[1][0][j] + E[1][0][j-1]) % mod;
		}
		for (int j = 1; j <= K; j++) {
			swap(E[0][0][j], E[0][1][j]);
			swap(E[1][0][j], E[1][1][j]);
		}
		for (int j = 1; j <= K; j++) {
			ans = (ans + 1ll * fac[j] * strl[K][j] % mod * (E[0][0][j] - E[1][0][j] + mod) % mod) % mod;
		}
	}
	cout << ans << '\n';
	return 0;
}

T4 - 惊世智慧

这场真是 mygo 场了。

Description

作为月之森点子王,Soyo 女士一直在密谋光复 C 团。

给定一棵有 \(n\) 个节点的有根树(根为 \(1\))。每次操作须选定树上的一个节点 \(u\) 与一个整数 \(k(k \in [0,n-1])\),使得 \(u\) 子树中到 \(u\) 的距离恰好为 \(k\) 的所有节点被点亮(节点可被重复点亮),并花费 \(a_k\)(正整数)的开销。求将树上所有节点都点亮所需的最小开销。

\(1 \le n \le 10^5\)

Reflection

场上注意到重复点亮一定不优,然后注意到答案的可并性,就弄出了一个对每层从下往上 DP 的方法。

Solution

可以逐层考虑。对于每层的所有节点,重复点亮一定不优。

注意到重复点亮存在且仅存在于某节点被选且该节点的祖先也被选的情况中。因此不能重复选择一个节点和它的祖先,这等价于一条树链上的节点会且仅会选择一个,这具有可并性,于是可以树上 DP。

遍历每一层,设当前层为 \(d\),记 \(dp_u\) 表示点亮 \(u\) 子树内所有深度为 \(d\) 的节点所需的最小开销。显然我们对于每个 \(u\),要么只选它的所有子节点,要么只选 \(u\) 自己。这样即有

\[dp_u \leftarrow \min\Big(\sum_{v \in \mathrm{son}_u} dp_v,\ a_{d - dep_u}\Big) \]

对每一层,把 \(dp_1\) 加入答案即可。

事实上这种做法存在相应的构造数据可以卡到 \(O(n^2)\),但出题人没卡,还挺良心的。

std 解法是长链剖分,不打算学。

Code

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define int long long
#define inf 1e18
#define debug cout << '!';
#define filein(x) freopen(#x".in", "r", stdin);
#define fileout(x) freopen(#x".out", "w", stdout);
#define file(x) filein(x) fileout(x)
using namespace std;
const int N = 1e5+5;
int n, a[N], ans;
vector<int> g[N];
int dep[N], maxdep;
void DFS1(int u, int fa) {
	for (auto v: g[u]) {
		if (v == fa) continue;
		dep[v] = dep[u] + 1;
		maxdep = max(maxdep, dep[v]);
		DFS1(v, u);
	}
}
int dp[N];
void DFS2(int u, int fa, int cur_d) {
	if (dep[u] == cur_d) dp[u] = a[0];
	if (dep[u] > cur_d) return;
	for (auto v: g[u]) {
		if (v == fa) continue;
		DFS2(v, u, cur_d);
		dp[u] += dp[v];
	}
	dp[u] = min(dp[u], a[cur_d-dep[u]]);
}
signed main() {
	cin.tie(0) -> sync_with_stdio(0);
	// file(wisdom);
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		g[u].push_back(v), g[v].push_back(u);
	}
	dep[1] = 1;
	DFS1(1, -1);
	for (int d = 1; d <= maxdep; d++) {
		memset(dp, 0, sizeof dp);
		DFS2(1, -1, d);
		ans += dp[1];
	}
	cout << ans;
	return 0;
}
posted @ 2026-07-31 16:16  L-Coding  阅读(6)  评论(0)    收藏  举报