闲话 23.3.30

闲话

HE 怎么又 15 个名额
乍一看像个强省
CQ 怎么 B2 = 19.9 B3 = 11
平均分高于全国 94.8 分?
基准分高于全国 50 分?
哦 BJ 和 CQ 都是春季赛
但是 ZJ 还是真nb

明天大概率没有闲话!
所以今天要推歌!
推歌:【洛天依AI】《海市蜃楼》多像个童话,刻画成我眼里的年华

模拟赛

摆!

T1 卷王

考虑差分异或 得到一个序列 a,即 a[i] 是原序列第 i,i1 两个位置的异或
t 秒按第 i 个开关会使得第 t+dta[i+dt],a[i+dt+1] 两个位置翻转,这变化会保存

可以发现的是,除了 a[i] 外,所有 i+p(p>0) 的位置都只有在第 t+p 的时刻翻转 而 a[i] 总已经被翻转了
所以如果确定了一个操作到现在的时间,我们可以轻易确定这个状态对现在 a 序列的影响
这样我们不妨设计一种状压 dp 来倒着枚举操作序列
f(t,S) 表示后 t 秒内(操作序列长度为 tS 状态是/否可以翻转到全 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(n22n)

我们没必要对每个长度都处理一遍答案
对于一个状态 S,在 S 前面加上任意多的 0 对答案没有影响
因为操作只会向后贡献
所以只需要处理长度为 maxn=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] 可行当且仅当 ki=lrai,原因显然
考虑对每个 r 统计合法的 l,这样的 l 可能有 O(n) 个,性质不太好
考虑对 a 作前缀和得到 s,则我们需要的就是 sr(modk)sl1

先不考虑整体咋算,考虑确定了区间 a[l,r] 如何计算贡献,记为 b[1,m]
从前往后考虑,对 b1 只有动 b2 可以修改 b1,这个操作数是 min(b1 mod k,kb1 mod k)
然后从前往后扫,对 b 作前缀和得到 t,到第 i 个元素时其实 bi=ti
所以对 b 的答案就是 i=1mmin(ti mod k,kti mod k)

考虑 a[l,r] 是可行子序列,并存在 k 满足 a[l,k],a[k+1,r] 是可行子序列
i=lka[i]=t,对 a[l,r] 作前缀和得到 s,我们还知道 kt
则我们知道答案 f(l,r) 就是

i=lrmin((sisl1) mod k,k(sisl1) mod k)= i=lkmin((sisl1) mod k,k(sisl1) mod k)+i>krmin((sisl1) mod k,k(sisl1) mod k)= f(l,k)+i>krmin((sisk+t) mod k,k(sisk+t) mod k)= f(l,k)+i>krmin((sisk) mod k,k(sisk) mod k)= f(l,k)+f(k+1,r)

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

然后考虑要算啥

i=lrmin((sisl1) mod k,k(sisl1) mod k)

可以发现,如果确定了 sl1,那这一段区间内不同的 si 对答案的贡献是确定的。所以我们对值域开桶,维护 si=vi 个数与 si,并需要支持区间查询。
这可以树状数组维护,每次讨论区间贡献即可。由于 si 范围 109,但可能的值只有 O(n) 个,我们还需要离散化,每次区间查询时在原范围上做讨论,并映射到离散化区间上。

总时间复杂度 O(nlogn)

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 + i0i 轮打不死 boss 的概率
= 1 + S 拿到的打不死 boss 的手牌是 S 的概率
所以考虑统计所有打不死 boss 的手牌和概率
顺序没有影响 所以考虑按 S 里有的手牌种类计数

先推个式子:

i01xi=i0(1x)i=111x=xx1

自然有

i11xi=xx11=1x1

  1. 只有复读
    6
    答案就是 i13i=1/2

  2. 只有火球
    每次打 2 点伤害,打 (n+1)/2 次 boss 就没了
    设 m = (n1)/2
    答案就是 i=1n3i=(1(1/3)m)/2

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

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

i=1mj=1i1(ij)(1/3)j(1/3)ij= i=1m(23)i2×(13)i= 1+12m+13m

  1. 火球 + 毒药
    最优策略肯定是第一张打毒药,剩下的毒药伤害是 1,火球伤害是 3
    我们给 boss 血量 + 1,钦定第一张打出去的毒药有伤害
    f(dmg) 是给 boss 打了 dmg 伤害的概率 答案就是 inf(i)
    dp 转移是 f(k)=f(k1)/3+f(k3)/3
    这可以矩阵快速幂优化 就是

[f(k)f(k1)f(k2)ans(k1)]=[1/301/30100001001001][f(k1)f(k2)f(k3)ans(k2)]

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

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

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

总时间复杂度 O(T43logn) ……吧?

没有代码!摆摆摆!

杂题

CF1119F

给定一棵 n 个结点的树,每条边 i 有边权 ci,结点度数 deg(u) 就是与 u 节点相连的边数量。对每个 0x<n,删掉一些边使每个结点的度数不大于 x,求出删掉的边的权值和最小值。

2n2.5×105, 1ci109

大佬们怎么都在 22.10.15 ± 3d 做了这题?

首先考虑确定 x 咋做。这好像很经典。
首先钦定 1 为根,并设 f(i,0) 表示保留 i 到父亲的边的情况下 i 子树合法的最小花费,f(i,1) 表示删掉 i 到父亲的边的情况下 i 子树合法的最小花费。
考虑 f(u) 咋算。首先 dfs,算出子树内信息。设 av=f(v,0)bv=f(v,1)+w(u,v),这样 f(u,0) 就是从 a 里取至多 x 个,从 b 里取至少 deg(u)x 个;f(u,1) 类似。这可以拿个堆贪心地做,先钦定子树全部选 av,然后用堆放入 bvav 做可反悔贪心。
这样的复杂度是单次 O(nlogn) 的,总复杂度 O(n2logn)

这样的复杂度瓶颈在于我们需要对 O(x=0n1i=1ndeg(i)) 个决策点做可反悔贪心,考虑如何减少这个量。
一个立即的思考是按度数从小到大做。也就是说,我们从小到大求 x,每次首先处理 deg(u)x 的点 u。可以知道这样的点集 {ui}x 增大时只会并入新元素,这样我们讨论 x1x 时的情况。
这时所有 deg(u)=x 的点 u 是需要预先处理掉的。我们知道,这些点总不会在 dfs 时对决策产生贡献,因为在决策这些点时一定不会删周围的边,他们只可能给周围的点新增决策点。考虑对每个点 u 维护一个全局的集合 Su,每次对 u 点和其儿子做可反悔贪心时集合的初值设为 Su。这样处理点 u 时,我们就可以给与 u 存在 (u,v,w) 边的所有点 v 的集合内加入 w 作为新的决策点。注意处理 u 时不会对周围点的度数产生影响。
随后我们的树上就有一系列需要 dfs 处理的点和剩下的不需要处理的点了,这些需要 dfs 处理的点形成了一系列连通块,我们对这些连通块分别做如上的 O(nlogn) dp 即可。
这时我们的可反悔贪心就需要维护一个集合 S,支持删 S 内最大值到 |S|V、查询 S 内元素和、插入元素。这可以用对顶堆实现,懒惰删除的复杂度是 O(logn) 单次的。

考虑分析复杂度?我们知道 dfs 中决策点的数量是 O(x=0n1i=1n[deg(i)>x])=O(i=1ndeg(i))=O(n) 的,并且全局决策点也是 O(n) 的,所以总时间复杂度是 O(nlogn) 的。

Submission.

posted @   joke3579  阅读(110)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示