闲话 23.3.30
闲话
HE 怎么又 15 个名额
乍一看像个强省
CQ 怎么 B2 = 19.9 B3 = 11
平均分高于全国 94.8 分?
基准分高于全国 50 分?
哦 BJ 和 CQ 都是春季赛
但是 ZJ 还是真nb
明天大概率没有闲话!
所以今天要推歌!
推歌:【洛天依AI】《海市蜃楼》多像个童话,刻画成我眼里的年华
模拟赛
摆!
T1 卷王
考虑差分异或 得到一个序列 a,即 \(a[i]\) 是原序列第 \(i, i - 1\) 两个位置的异或
第 \(t\) 秒按第 \(i\) 个开关会使得第 \(t + \text{dt}\) 秒 \(a[i + \text{dt}], a[i + \text{dt} + 1]\) 两个位置翻转,这变化会保存
可以发现的是,除了 \(a[i]\) 外,所有 \(i + p (p > 0)\) 的位置都只有在第 \(t + p\) 的时刻翻转 而 \(a[i]\) 总已经被翻转了
所以如果确定了一个操作到现在的时间,我们可以轻易确定这个状态对现在 a 序列的影响
这样我们不妨设计一种状压 dp 来倒着枚举操作序列
设 \(f(t, S)\) 表示后 \(t\) 秒内(操作序列长度为 \(t\)) \(S\) 状态是/否可以翻转到全 0
初始 f(0, 00...0) = true;
每次枚举状态 f(t, S) = true,并枚举要加入到操作序列首的操作
可以是不操作,即 f(t + 1, S) = true
然后枚举当前操作位置为 p,我们知道这次操作肯定翻转 \(a[p]\)
并且由于这次操作到当前操作数/时间为 \(t + 1\) 可以知道 \(a[t + 1 + p]\) 也被翻转
这 dp 是 \(O(n^2 2^n)\) 的
我们没必要对每个长度都处理一遍答案
对于一个状态 S,在 S 前面加上任意多的 0 对答案没有影响
因为操作只会向后贡献
所以只需要处理长度为 \(\max n = 16\) 的即可
并且,打表可以发现最大操作次数不会超过 8,我们能把每两个答案压进一个可见字符
这样代码长度 \le 40k,总复杂度 O(2^n + Tn)(
code
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define inline __attribute__((__gnu_inline__, __always_inline__, __artificial__)) inline
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 20 + 10, M = 1 << 16 | 3;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int V = (1 << 16) - 1, ans[M], a[N], f[17][M];
char ch[N];
signed main(){
f[0][0] = 1;
rep(t,0,15) rep(S,0,V) if (f[t][S]) {
f[t + 1][S] = 1;
rep(p,0,15) {
int Si = S ^ (1 << p);
if (p + t + 1 <= 15) Si ^= 1 << (t + p + 1);
f[t + 1][Si] = 1;
}
}
rep(S,0,V) rep(t,0,15) if (f[t][S]) { ans[S] = t; break; }
// for (int i = 0; i <= V; i += 2) {
// cout << (char)(' ' + ans[i] * 9 + ans[i + 1]);
// }
// for (int i = 0, t = 0; i <= V; i += 2, ++ t) {
// gr[t] -= ' ';
// ans[i] = gr[t] / 9, ans[i + 1] = gr[t] % 9;
// }
multi {
cin >> ch + 1; int top = 0, l = strlen(ch + 1);
rep(i,l+1,16) a[++ top] = 0;
rep(i,1,l) a[++ top] = ch[i] - '0';
pre(i, top, 1) a[i] ^= a[i - 1];
int stat = 0;
rep(i,1,top) stat |= (a[i] << i - 1);
cout << ans[stat] << '\n';
}
}
// while (1) {
// cin >> ch + 1; int pos, l = strlen(ch + 1); cin >> pos;
// rep(p,pos - 1,l) {
// cout << "Dt = " << p - pos + 1 << '\n';
// if (p >= pos) ch[p] ^= 1;
// cout << ch + 1 << '\n';
// rep(i,1,l) a[i] = ch[i] - '0';
// pre(i,l,1) a[i] ^= a[i - 1];
// rep(i,1,l) cout << a[i] << ' ';
// cout << '\n' << '\n';
// }
// }
T2 赢王
首先 \(a[l, r]\) 可行当且仅当 \(k \mid \sum_{i = l}^r a_i\),原因显然
考虑对每个 \(r\) 统计合法的 \(l\),这样的 \(l\) 可能有 \(O(n)\) 个,性质不太好
考虑对 \(a\) 作前缀和得到 \(s\),则我们需要的就是 \(\equiv s_r \pmod k\) 的 \(s_{l - 1}\)
先不考虑整体咋算,考虑确定了区间 \(a[l, r]\) 如何计算贡献,记为 \(b[1, m]\)
从前往后考虑,对 \(b_1\) 只有动 \(b_2\) 可以修改 \(b_1\),这个操作数是 \(\min(b_1 \text{ mod } k, k - b_1 \text{ mod } k)\) 的
然后从前往后扫,对 \(b\) 作前缀和得到 \(t\),到第 i 个元素时其实 \(b_i = t_i\)
所以对 \(b\) 的答案就是 \(\sum_{i = 1}^m \min(t_i \text{ mod } k, k - t_i \text{ mod } k)\)
考虑 \(a[l, r]\) 是可行子序列,并存在 \(k\) 满足 \(a[l, k], a[k + 1, r]\) 是可行子序列
设 \(\sum_{i = l}^k a[i] = t\),对 \(a[l, r]\) 作前缀和得到 s,我们还知道 \(k \mid t\)
则我们知道答案 \(f(l, r)\) 就是
这样我们就有了平凡的 \(O(nk)\) 做法
首先按 \(s_i \text{ mod } k\) 分组,组数是 \(O(k)\) 的
然后我们对每组直接 \(O(n)\) 处理出相邻点间的答案
一段的贡献就是包含他的区间个数,这个平凡算
然后加上没贡献的区间的 \(-1\) 即可
大概是 60pts
如果数据水可以多过几个包
然后考虑要算啥
可以发现,如果确定了 \(s_{l - 1}\),那这一段区间内不同的 \(s_i\) 对答案的贡献是确定的。所以我们对值域开桶,维护 \(s_i = v\) 的 \(i\) 个数与 \(\sum s_i\),并需要支持区间查询。
这可以树状数组维护,每次讨论区间贡献即可。由于 \(s_i\) 范围 \(10^9\),但可能的值只有 \(O(n)\) 个,我们还需要离散化,每次区间查询时在原范围上做讨论,并映射到离散化区间上。
总时间复杂度 \(O(n\log n)\)。
code
#include <bits/stdc++.h>
using namespace std;
#define inline __attribute__((__gnu_inline__, __always_inline__, __artificial__)) inline
using pii = pair<int,int>; using vi = vector<int>; using vp = vector<pii>; using ll = long long;
using ull = unsigned long long; using db = double; using ld = long double; using lll = __int128_t;
template<typename T1, typename T2> T1 max(T1 a, T2 b) { return a > b ? a : b; }
template<typename T1, typename T2> T1 min(T1 a, T2 b) { return a < b ? a : b; }
#define multi int _T_; cin >> _T_; for (int TestNo = 1; TestNo <= _T_; ++ TestNo)
#define timer cerr << 1. * clock() / CLOCKS_PER_SEC << '\n';
#define iot ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
#define eb emplace_back
#define pb pop_back
const int N = 1e5 + 10, mod = 998244353;
const int inf = 0x3f3f3f3f;
const ll infll = 0x3f3f3f3f3f3f3f3fll;
int n, k, a[N], b[N], s[N], lsh[N], hrt, lsh_s[N], ans, cnt[N], buk[N];
#define find(u) (lower_bound(lsh + 1, lsh + 1 + hrt, u) - lsh)
#define find2(u) (upper_bound(lsh + 1, lsh + 1 + hrt, u) - lsh - 1)
inline int norm(int x) { x >= mod ? x -= mod : 0; return x; }
struct fenwick {
pii Index[N];
inline void add(int p, int v) {
int va = 1ll * v * lsh[p] % mod;
for (; p <= hrt; p += p & -p) Index[p].first += v, Index[p].second = norm(Index[p].second + va);
}
inline pii query(int p) {
pii ret = { 0, 0 };
for (; p > 0; p ^= p & -p) ret.first = norm(ret.first + Index[p].first), ret.second = norm(ret.second + Index[p].second);
return ret;
}
inline int query(int l, int r, int coef1, int coef2) {
pii lp = query(l - 1), rp = query(r);
rp.first = norm(rp.first - lp.first + mod);
rp.second = norm(rp.second - lp.second + mod);
return (1ll * coef1 * rp.second + 1ll * coef2 * rp.first) % mod;
}
} Tr;
signed main() {
cin >> n >> k;
rep(i,1,n) cin >> a[i], b[i] = a[i] % k, s[i] = (s[i - 1] + b[i]) % k;
rep(i,1,n) lsh[i] = s[i]; sort(lsh + 1, lsh + 1 + n);
hrt = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
rep(i,0,n) lsh_s[i] = find(s[i]), ans = norm(ans + buk[lsh_s[i]]), buk[lsh_s[i]]++;
Tr.add(lsh_s[0], --buk[lsh_s[0]]);
cnt[1] = 1;
rep(i,1,n) {
ans = norm(ans + Tr.query(find(lsh[lsh_s[i]] - k / 2), lsh_s[i], lsh[lsh_s[i]], -1));
ans = norm(ans + Tr.query(find(lsh[lsh_s[i]] + (k + 1) / 2), hrt, lsh[lsh_s[i]] + k, -1));
ans = norm(ans + Tr.query(1, find2(lsh[lsh_s[i]] - k / 2 - 1), k - lsh[lsh_s[i]], 1));
ans = norm(ans + Tr.query(lsh_s[i] + 1, find2(lsh[lsh_s[i]] + (k + 1) / 2 - 1), -lsh[lsh_s[i]], 1));
buk[lsh_s[i]]--;
cnt[lsh_s[i]]++;
int deltnum = buk[lsh_s[i]] - cnt[lsh_s[i]] + 1;
Tr.add(lsh_s[i], deltnum);
} cout << ans << '\n';
}
T3 稳王
什么组合数?
期望回合
= 1 + \(\sum_{i\ge 0}\) 第 \(i\) 轮打不死 boss 的概率
= 1 + \(\sum_{S}\) 拿到的打不死 boss 的手牌是 \(S\) 的概率
所以考虑统计所有打不死 boss 的手牌和概率
顺序没有影响 所以考虑按 S 里有的手牌种类计数
先推个式子:
自然有
-
只有复读
6
答案就是 \(\sum_{i\ge 1} 3^{-i} = 1/2\) -
只有火球
每次打 2 点伤害,打 \((n + 1) / 2\) 次 boss 就没了
设 m = \((n - 1) / 2\)
答案就是 \(\sum_{i = 1}^n 3^{-i} = (1 - (1/3)^m) / 2\) -
只有毒药
每次打一张毒药 打 \(n + 1\) 次 boss 就没了
答案就是 \(\sum_{i, 1, n} 3^{-i} = (1 - (1/3)^n) / 2\) -
复读 + 火球
只要有火球,那复读 = 火球
全是复读和全是火球的已经统计完了
那答案就是
- 火球 + 毒药
最优策略肯定是第一张打毒药,剩下的毒药伤害是 1,火球伤害是 3
我们给 boss 血量 + 1,钦定第一张打出去的毒药有伤害
设 \(f(dmg)\) 是给 boss 打了 \(dmg\) 伤害的概率 答案就是 \(\sum_{i\le n} f(i)\)
dp 转移是 \(f(k) = f(k - 1) / 3 + f(k - 3) / 3\)
这可以矩阵快速幂优化 就是
-
复读 + 毒药
毒药伤害是 1,复读伤害是 2
如上 dp 即可 -
三种都有
仍然是直接维护矩阵快速幂
值得注意的是,最终需要做一些容斥,比方说 5. 需要删掉全是毒药的情况
总时间复杂度 \(O(T 4^3 \log n)\) ……吧?
没有代码!摆摆摆!
杂题
给定一棵 \(n\) 个结点的树,每条边 \(i\) 有边权 \(c_i\),结点度数 \(deg(u)\) 就是与 \(u\) 节点相连的边数量。对每个 \(0 \le x < n\),删掉一些边使每个结点的度数不大于 \(x\),求出删掉的边的权值和最小值。
\(2\le n\le 2.5\times 10^5, \ 1\le c_i\le 10^9\)。
大佬们怎么都在 22.10.15 \(\pm\) 3d 做了这题?
首先考虑确定 \(x\) 咋做。这好像很经典。
首先钦定 \(1\) 为根,并设 \(f(i, 0)\) 表示保留 \(i\) 到父亲的边的情况下 \(i\) 子树合法的最小花费,\(f(i, 1)\) 表示删掉 \(i\) 到父亲的边的情况下 \(i\) 子树合法的最小花费。
考虑 \(f(u)\) 咋算。首先 dfs,算出子树内信息。设 \(a_v = f(v, 0)\),\(b_v = f(v, 1) + w(u, v)\),这样 \(f(u, 0)\) 就是从 \(a\) 里取至多 \(x\) 个,从 \(b\) 里取至少 \(deg(u) - x\) 个;\(f(u, 1)\) 类似。这可以拿个堆贪心地做,先钦定子树全部选 \(a_v\),然后用堆放入 \(b_v - a_v\) 做可反悔贪心。
这样的复杂度是单次 \(O(n\log n)\) 的,总复杂度 \(O(n^2 \log n)\)。
这样的复杂度瓶颈在于我们需要对 \(O\left(\sum_{x = 0}^{n - 1} \sum_{i = 1}^n deg(i)\right)\) 个决策点做可反悔贪心,考虑如何减少这个量。
一个立即的思考是按度数从小到大做。也就是说,我们从小到大求 \(x\),每次首先处理 \(deg(u) \le x\) 的点 \(u\)。可以知道这样的点集 \(\{u_i\}\) 在 \(x\) 增大时只会并入新元素,这样我们讨论 \(x - 1\to x\) 时的情况。
这时所有 \(deg(u) = x\) 的点 \(u\) 是需要预先处理掉的。我们知道,这些点总不会在 dfs 时对决策产生贡献,因为在决策这些点时一定不会删周围的边,他们只可能给周围的点新增决策点。考虑对每个点 \(u\) 维护一个全局的集合 \(S_u\),每次对 \(u\) 点和其儿子做可反悔贪心时集合的初值设为 \(S_u\)。这样处理点 \(u\) 时,我们就可以给与 \(u\) 存在 \((u, v, w)\) 边的所有点 \(v\) 的集合内加入 \(w\) 作为新的决策点。注意处理 \(u\) 时不会对周围点的度数产生影响。
随后我们的树上就有一系列需要 dfs 处理的点和剩下的不需要处理的点了,这些需要 dfs 处理的点形成了一系列连通块,我们对这些连通块分别做如上的 \(O(n\log n)\) dp 即可。
这时我们的可反悔贪心就需要维护一个集合 \(S\),支持删 \(S\) 内最大值到 \(|S| \le V\)、查询 \(S\) 内元素和、插入元素。这可以用对顶堆实现,懒惰删除的复杂度是 \(O(\log n)\) 单次的。
考虑分析复杂度?我们知道 dfs 中决策点的数量是 \(O\left(\sum_{x = 0}^{n - 1} \sum_{i = 1}^n [deg(i) > x]\right) = O\left(\sum_{i = 1}^n deg(i)\right) = O(n)\) 的,并且全局决策点也是 \(O(n)\) 的,所以总时间复杂度是 \(O(n\log n)\) 的。
以下是博客签名,与正文无关。
请按如下方式引用此页:
本文作者 joke3579,原文链接:https://www.cnblogs.com/joke3579/p/chitchat230330.html。
遵循 CC BY-NC-SA 4.0 协议。
请读者尽量不要在评论区发布与博客内文完全无关的评论,视情况可能删除。