闲话 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)\) 就是

\[\begin{aligned} &\sum_{i = l}^r \min((s_i - s_{l - 1})\text{ mod } k, k - (s_i - s_{l - 1}) \text{ mod } k) \\ = \ & \sum_{i = l}^k \min((s_i - s_{l - 1}) \text{ mod } k, k - (s_i - s_{l - 1}) \text{ mod } k) + \sum_{i > k}^r \min((s_i - s_{l - 1}) \text{ mod } k, k - (s_i - s_{l - 1}) \text{ mod } k) \\ = \ & f(l, k) + \sum_{i > k}^r \min((s_i - s_{k} + t) \text{ mod } k, k - (s_i - s_{k} + t) \text{ mod } k) \\ = \ & f(l, k) + \sum_{i > k}^r \min((s_i - s_{k}) \text{ mod } k, k - (s_i - s_{k}) \text{ mod } k) \\ = \ & f(l, k) + f(k + 1, r) \end{aligned}\]

这样我们就有了平凡的 \(O(nk)\) 做法
首先按 \(s_i \text{ mod } k\) 分组,组数是 \(O(k)\)
然后我们对每组直接 \(O(n)\) 处理出相邻点间的答案
一段的贡献就是包含他的区间个数,这个平凡算
然后加上没贡献的区间的 \(-1\) 即可
大概是 60pts
如果数据水可以多过几个包

然后考虑要算啥

\[\sum_{i = l}^r \min((s_i - s_{l - 1})\text{ mod } k, k - (s_i - s_{l - 1}) \text{ mod } k) \]

可以发现,如果确定了 \(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 里有的手牌种类计数

先推个式子:

\[\sum_{i\ge 0} \frac{1}{x^i} = \sum_{i\ge 0} \left(\frac{1}{x}\right)^i = \frac{1}{1 - \frac{1}{x}} = \frac{x}{x - 1} \]

自然有

\[\sum_{i\ge 1} \frac{1}{x^i} = \frac{x}{x - 1} - 1 = \frac{1}{x - 1} \]

  1. 只有复读
    6
    答案就是 \(\sum_{i\ge 1} 3^{-i} = 1/2\)

  2. 只有火球
    每次打 2 点伤害,打 \((n + 1) / 2\) 次 boss 就没了
    设 m = \((n - 1) / 2\)
    答案就是 \(\sum_{i = 1}^n 3^{-i} = (1 - (1/3)^m) / 2\)

  3. 只有毒药
    每次打一张毒药 打 \(n + 1\) 次 boss 就没了
    答案就是 \(\sum_{i, 1, n} 3^{-i} = (1 - (1/3)^n) / 2\)

  4. 复读 + 火球
    只要有火球,那复读 = 火球
    全是复读和全是火球的已经统计完了
    那答案就是

\[\begin{aligned} & \sum_{i = 1}^m \sum_{j = 1}^{i - 1} \binom{i}{j} (1/3)^{j} (1/3)^{i - j} \\ = \ & \sum_{i = 1}^m \left(\frac{2}{3}\right)^i - 2\times \left(\frac{1}{3}\right) ^i \\ = \ & 1 + \frac{1 - 2^{m + 1}}{3^m} \end{aligned}\]

  1. 火球 + 毒药
    最优策略肯定是第一张打毒药,剩下的毒药伤害是 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\)
    这可以矩阵快速幂优化 就是

\[\begin{bmatrix} f(k) \\ f(k - 1) \\ f(k - 2) \\ ans(k - 1) \end{bmatrix} = \begin{bmatrix} 1/3 & 0 & 1/3 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 1 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} f(k - 1) \\ f(k - 2) \\ f(k - 3) \\ ans(k - 2) \end{bmatrix} \]

  1. 复读 + 毒药
    毒药伤害是 1,复读伤害是 2
    如上 dp 即可

  2. 三种都有
    仍然是直接维护矩阵快速幂

值得注意的是,最终需要做一些容斥,比方说 5. 需要删掉全是毒药的情况

总时间复杂度 \(O(T 4^3 \log n)\) ……吧?

没有代码!摆摆摆!

杂题

CF1119F

给定一棵 \(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)\) 的。

Submission.

posted @ 2023-03-30 12:07  joke3579  阅读(99)  评论(0编辑  收藏  举报