2025-11-7 10:20:00 TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.

CZOI Round 3

因为你 生来就形只影单

才将呼救向世界呐喊

剥离鲜明色彩隐没在 声浪层叠

我们是否等来许诺的晴天

曾妄想借音乐唤回的温暖

蒙头奔跑吗 明天

寻回纯粹吧 尽管孑然

CZOI Round 3

可爱棉羊

最小值是相互传染,答案是 \(x\),但 \(x = 1\) 时需要单独传染一个,答案是 \(2\)

一个被感染的小羊多轮传染形似向两边染色,第一轮只能染一边,故一个被感染的小羊的贡献为 \(2t\),答案为 \(\min(2xt,n)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
  x = 0; int f = 1; char ch = getchar();
  while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
  while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
  x *= f; read(arg...);
}

void write() {}
template<typename T> void write(T x) {
  if (x < 0) { putchar('-'), x = -x; }
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}

int n, t, x;

int main() {
#ifndef ONLINE_JUDGE
  freopen("code.in", "r", stdin);
  freopen("code.out", "w", stdout);
#endif
  read(n, t, x);
  i64 Min = x == 1 ? 2 : x, Max = min(i64(n), i64(2) * t * x);
  write(Max); putchar(' '); write(Min);
  return 0;
}

// START AT 2025 / 05 / 03 14 : 33 : 19

星光闪耀

首先观察出题目求

\[\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_m = 1}^{i_{m - 1}}k^{i_m} \]

根据等比数列求和将最后一项展开,可得

\[\begin{align} \sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_m = 1}^{i_{m - 1}}k^{i_m}&=\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}(k^{i_{m - 1} + 1} - k)}{k - 1}\\ &=\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k^{i_{m - 1} + 1} - \sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k}{k - 1}\\ &=\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k^{i_{m - 1} + 1} - k \times \sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}1}{k - 1}\\ \end{align} \]

\(\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k^{i_{m - 1} + 1}}{k - 1}\)可以按上面化简。递归处理即可。考虑求 \(\sum\limits_{i_1 = 1}^n\sum\limits_{i_2 = 1}^{i_1}\cdots\sum\limits_{i_p = 1}^{i_{p - 1}}1\)

这相当于求单调不增的 \(\{i_p\}\) 的个数,定义 \(a_j\) 表示 \(j\)\(\{i_p\}\) 中出现的次数,则转化为求 \(\{a_n\}\),满足 \(\sum\limits_{i = 1}^na_i = p\)

相当于有 \(p\) 个元素,要求分成 \(n\) 组,每组允许为空。

能用插板法解决,答案是 \(\begin{pmatrix}p + n - 1\\n - 1\end{pmatrix}=\begin{pmatrix} n + p - 1\\ p \\ \end{pmatrix}\)

\[\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_m = 1}^{i_{m - 1}}k^{i_m}=\dfrac{\sum_{i_1 = 1}^n\sum_{i_2 = 1}^{i_1}\cdots\sum_{i_{m - 1} = 1}^{i_{m - 2}}k^{i_{m - 1} + 1} - k \times \begin{pmatrix} n + m - 2\\ m - 1 \\ \end{pmatrix}}{k - 1} \]

因为出题人说会卡常所以要用递推实现

展开 \(m\) 项后得到 \(\dfrac{k^{n + m} - k^m}{k - 1}\),之后递推。

时间复杂度 \(\mathcal{O}(N+\sum m)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

#define int long long

void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
	x = 0; int f = 1; char ch = getchar();
	while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	x *= f; read(arg...);
}

void write() {}
template<typename T> void write(T x) {
	if (x < 0) { putchar('-'), x = -x; }
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

const int mod = 998244353;
const int N = 4e6 + 5;

int fac[N], inv[N], ifac[N];
int T, n, m, k;

int Pow(int a, int b) {
	int res = 1;

	for (; b; b >>= 1) {
		if (b & 1) res = 1LL * res * a % mod;
		a = 1LL * a * a % mod;
	}

	return res;
}

int binom(int n, int m) {
	return 1LL * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}

void solve() {
	read(n, m, k);
  
	if (k == 1) {
		write(binom(n + m - 1, m)); putchar('\n');
	} else {
		int inv1 = Pow(k - 1, mod - 2), inv2 = Pow(k, mod - 2), ans = Pow(k, n + m);
		k = Pow(k, m);
    
		for (int i = 1; i <= m; i++) {
			ans = (1LL * ans - 1LL * binom(n + i - 2, i - 1) * k % mod + mod) % mod * inv1 % mod;
			k = 1LL * k * inv2 % mod;
		}

		write(ans); putchar('\n');
	}
}

signed main() {
#ifndef ONLINE_JUDGE
	freopen("code.in", "r", stdin);
	freopen("code.out", "w", stdout);
#endif
	
	fac[0] = ifac[0] = inv[1] = 1;
	for (int i = 2; i <= N - 5; i++) inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;
	for (int i = 1; i <= N - 5; i++)
		fac[i] = 1LL * fac[i - 1] * i % mod,
		ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
	read(T);
	while (T--) solve();
	return 0;
}

// START AT 2025 / 05 / 03 22 : 35 : 12

消除序列

显然,令 \(a\) 中元素变 \(0\) 的顺序仅与 \(b\) 相关。只需要考虑怎么移。

显然不能移动 \(a\) 排列,用指针 \(h\) 表示 \(a_1\) 所在的位置就好。

考虑动态规划,除了交换 \(x, y\) 对后面有影响,别的都没有,单独开一维,定义 \(f(i, 0/1)\) 表示处理了原 \(a_i\) 有/没有换 \(x, y\) 的最小代价。

定义 \(L(i)\) 表示从 \(h\) 右移到 \(b_i\)\(a\) 当前位置需要的有代价步数。\(R(i)\) 表示从 \(h\) 左移到 \(b_i\)\(a\) 当前位置需要的有代价步数

\[f(i,0) = \min\{f(i - 1, 0) + L(i)\times x,\ f(i - 1,0)+R(i)\times y,\ f(i - 1,1)+L(i)\times y+z, f(i - 1, 1) + R(i) \times x + z\}\\ f(i,1) = \min\{f(i - 1, 0) + L(i)\times y + z,\ f(i - 1,0)+R(i)\times x + z,\ f(i - 1,1)+L(i)\times y, f(i - 1, 1) + R(i) \times x\} \]

所以求出 \(L(i), R(i)\) 就解决问题了。

有代价步数是 \(h\) 移动路径上 \(a_h \not= 0\) 的个数。这用线段树,树状数组之类的很好做。

时间复杂度是 \(\mathcal{O}(n \log n)\)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
  x = 0; int f = 1; char ch = getchar();
  while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
  while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
  x *= f; read(arg...);
}

void write() {}
template<typename T> void write(T x) {
  if (x < 0) { putchar('-'), x = -x; }
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}

const int N = 1e6 + 5;

#define lowbit(x) (x & (-x))

int n, x, y, z, tr[N], head = 1;
int a[N], b[N], c[N];
i64 f[N][2];

void add(int u, int d) {
  for (; u <= n; u += lowbit(u)) tr[u] += d;
}

int query(int u) {
  int res = 0;
  for (; u; u -= lowbit(u)) res += tr[u];
  return res;
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("code.in", "r", stdin);
  freopen("code.out", "w", stdout);
#endif

  read(n, x, y, z);
  for (int i = 1; i <= n; i++) add(i, 1);

  for (int i = 1; i <= n; i++) {
    read(a[i]); c[a[i]] = i;
  }

  for (int  i = 1; i <= n; i++) read(b[i]);

  f[0][1] = 1e18;

  for (int i = 1; i <= n; i++) {
    int left, right;
    if (head <= c[b[i]]) {
      left = query(c[b[i]] - 1) - query(head - 1);
      right = query(head) + query(n) - query(c[b[i]]);
    } else {
      left = query(c[b[i]] - 1) + query(n) - query(head - 1);
      right = query(head) - query(c[b[i]]);
    }

    f[i][0] = min(f[i - 1][0] + 1LL * left * x, f[i - 1][0] + 1LL * right * y);
    f[i][0] = min(f[i][0], min(f[i - 1][1] + 1LL * left * x + z, f[i - 1][1] + 1LL * right * y + z));
    f[i][1] = min(f[i - 1][0] + 1LL * left * y + z, f[i - 1][0] + 1LL * right * x + z);
    f[i][1] = min(f[i][1], min(f[i - 1][1] + 1LL * left * y, f[i - 1][1] + 1LL * right * x));
    
    add(c[b[i]], -1);
    head = c[b[i]];
  }

  write(min(f[n][0], f[n][1]));
  return 0;
}

// START AT 2025 / 05 / 11 22 : 37 : 23

数字变换

定义 \(f(i, j)\) 表示第 \(i\) 次令 \(a = j\) 的最小值。由题意

\[f(i, j) = w_{j,i} + 2L + \min_{l = 1}^{n}\{f(i - 1, l) -2\times(x_l\operatorname{AND}x_j)\} \]

考虑通过 \(x_l\) 简化转移。定义 \(g\)

\[g_m = \min_{x_l = m} \{f(i - 1, l)\} \]

\[f(i, j) = w_{j,i} + 2L + \min_{x_l = p}^{V}\{a_p -2\times(p\operatorname{AND}x_j)\} \]

接着化简,令 \(q = p \operatorname{AND} x_j\),则 \(q \subset p,q\sub x_j\)。考虑枚举 \(q\)

\[f(i, j) = w_{j, i} + 2L + \min_{q \sub x_j}\{\min_{p \supset q}\{a_p - 2q\}\} \]

可以先用高维后缀 \(\min\),求出 \(\min_{p \supset q}\{a_p - 2q\}\),存到 \(a_q\) 中。

然后用高维前缀 \(\min\) 求出 \(\min_{q \sub x_j} {a_p}\) 存到 \(a_p\) 中。转移就做完了。

时间复杂度是 \(\mathcal{O}(kV\log V)\)。(\(V\)\(x\) 值域)。

点击查看代码
#include <bits/stdc++.h>

using namespace std;

void read() {}
template<typename T, typename ...U> void read(T &x, U& ...arg) {
  x = 0; int f = 1; char ch = getchar();
  while (ch > '9' || ch < '0') { if (ch == '-') f = -1; ch = getchar(); }
  while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
  x *= f; read(arg...);
}

void write() {}
template<typename T> void write(T x) {
  if (x < 0) { putchar('-'), x = -x; }
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}

const int N = 2e5 + 5;

#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define per(i, a, b) for (int i = (a); i >= (b); i--)

int n, p, k, w[N][11], f[15][N], a[15][(1 << 16) + 5];
int c, y[65536], x[N], L;
unsigned long long seed;

int get_rand(int mod) {
  seed ^= seed << 14;
  seed ^= seed >> 7;
  seed ^= seed << 19;
  seed ^= seed << 23;
  return seed % mod;
}

void get_input() {
  rep(i, 1, n) x[i] = y[get_rand(c)];
  rep(i, 1, n) rep(j, 1, k) w[i][j] = get_rand(1000000);
}

signed main() {
#ifndef ONLINE_JUDGE
  freopen("code.in", "r", stdin);
  freopen("code.out", "w", stdout);
#endif
  read(n, p, k, c, seed);
  rep(i, 0, c - 1) read(y[i]);
  get_input();
  rep(i, 1, n) L = max(L, x[i]);

  memset(f, 0x3f, sizeof(f));
  memset(a, 0x3f, sizeof(a));
  f[0][p] = 0;

  rep(i, 1, k) {
    rep(j, 1, n) a[i][x[j]] = min(a[i][x[j]], f[i - 1][j]);
    rep(j, 0, 15) rep(l, 0, (1 << 16) - 1)
      if (!(l >> j & 1)) a[i][l] =min(a[i][l], a[i][l | (1 << j)]);
    rep(j, 0, (1 << 16) - 1) a[i][j] -= 2 * j;
    rep(j, 0, 15) rep(l, 0, (1 << 16) - 1) 
      if (l >> j & 1) a[i][l] = min(a[i][l], a[i][l ^ (1 << j)]); 
    rep(j, 1, n) f[i][j] = w[j][i] + a[i][x[j]] + 2 * L;
  }

  rep(i, 1, n) write(f[k][i]), putchar(' ');
  return 0;
}

// START AT 2025 / 06 / 15 20 : 41 : 28

总结

T1 < T3 < T4 = T2

T1,T3是可做题,下次看到容易用数据结构解决的问题要快点。需要练数据结构优化 dp。

T2 很困难倒也说不上,但是一开始就想错了,然后死磕。为什么我总是死磕最难题

需要练习组合数学,多做思维题。

T4感觉是很好的题。以后做 dp 可以尝试不断对转移方程化简。

看到位运算,dp 可以往高维前缀和想。

补题间隔太久远了。虽然中间发生了一些问题吧……以后不会了。

尽管环境不好,但还是希望我能像摘要里写的一样

寻回纯粹吧 尽管孑然

posted @ 2025-05-04 08:06  FRZ_29  阅读(38)  评论(0)    收藏  举报