假期做题记录

record

L i P R A C C M T F

sublime

Tools
 -> Developer
 -> New Snippet...
<snippet>
    <content><![CDATA[
#include <bits/stdc++.h>
using namespace std;

int main()
{
    return 0;
}
    ]]></content>

    <tabTrigger>cpp</tabTrigger>

    <scope>source.c++</scope>
</snippet>
    
    
save in:
Preferences
 -> Browse Packages
 -> User
    
// 一键编译
{
    "shell_cmd": "g++ $file_name -o $file_base_name && start cmd /k $file_base_name",
    "working_dir": "$file_path"
}
/*
考场记忆口诀(针对推荐版)
只需要在脑子里默念这三个部分:
编译它:g++ $file_name -o $file_base_name
成功就运行:&&
弹黑框保留:start cmd /k $file_base_name
最后提醒:
使用这种极简版,你的文件夹名字和 cpp 文件名里绝对不能有空格! (其实这也是各大算法竞赛的硬性规定,所以非常安全)。祝你考试顺利!

*/

\(\sum _x C(x,y) \times C(n-x,z) = C(n + 1,y+z+1)\)

Auto Compare

void solvemain()
{
    ifstream f1("x.out");
    ifstream f2("2.out");
    int line = 0 ;
    while (1)
    {
        string s1,s2;
        if (!getline(f1,s1) && !getline(f2,s2)) break;
        line ++ ;
        while (sz(s1) && s1.back() <=' ') s1.pop_back();
        while (sz(s2) && s2.back() <= ' ') s2.pop_back();
        if (s1 != s2)
        {
            cout << "Wa on line : " << line << endl;
            cout << "out : " << s1 << endl;
            cout << "ans : " << s2 << endl;
            return;
        }
    }
}
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());

list

l.push_back(x)
l.push_front(x)
auto it = l.begin();
s.insert(it,x);// 在it前插入

P13780 「o.OI R2」愿天堂没有分块

求极小mex区间

for(int i = 1; i <= n; i++){
    // 1. 框定合法的左端点范围 [L, R]
    int L = mps[a[i]] + 1; 
    int R = min(i, sgt::qrymn(1, 0, a[i] - 1));
    
    // 2. 更新当前数值的最后出现位置
    mps[a[i]] = i;
    sgt::mdf(1, a[i], i);
    
    // 3. 特判 MEX = 0 的情况(只有非零数自身构成的长度为1的区间 MEX 才为 0)
    if(a[i]) rgs[i].emplace_back(mkp(i, 0));
    
    // 4. 从最靠右的合法左端点 R 开始,不断向左跳跃寻找
    int r = R, l = 0;
    while(r >= L){
        // 直接查询 [r, i] 区间的 MEX
        int mex = sgt::getmex(r); 
        rgs[i].emplace_back(mkp(r, mex));
        
        // 为了让 MEX 继续变大,左端点必须向左延伸,把当前的 mex 包含进来
        // 所以下一次的左端点最大只能是 mps[mex]。
        l = max(L, mps[mex] + 1); 
        r = l - 1; // 相当于 r = min(L - 1, mps[mex]) 的效果,配合 while 循环自动退出
    }
}

时间复杂度\(O(n \log n)\),因为总共只有 \(O(n)\) 个支配对,每次线段树二分查找只花费 \(O(\log n)\)

用r从左往右边扫,考虑那些加入 \(a_r\) 后 mex 会变化的区间。

求完扫描线

https://www.luogu.com.cn/problem/CF2045E

//

A. 蜗蜗的魔法序列

异或比大小想到按位考虑。

\(a_i ,a_j\) 异或大小只与他们的 \(LCS\) 有关。

再发现每位独立。

https://newoj.daimayuan.top/p/5479?tid=6a66b63266c7fa8af222dbc1

C

$ \binom{m}{r}\binom{r}{k}=\binom{m}{k}\binom{m-k}{r-k} $

推式子中将两个求和号的式子合在一起可简化式子,枚举并限制会使问题复杂化。

https://newoj.daimayuan.top/p/5480?tid=6a66b63266c7fa8af222dbc1

D

先想到倒着考虑从 \(s\) 出发,然后考虑倒着扫描边半径 \(d\)

考虑维护 \(f_x\) 表示从s 到 x的d之前,最少步数。

看加入在 \(d\) 的这条边后会发生什么变化。

若不经过 \(d\) ,除 \(t,t+1\) 以外的可以做到,因此继承 \(f_x\)

对于这些边,还可以经过 \(d\),则用 \(f_t ,f_{t + 1}\) 更新。

对于 \(t,t+1\) 经过 \(d\),他们的 dp 值会交换。

对于 \(t,t+1\) 不经过 \(d\),则他们必然用 \(f_{t-1},f_{t+2}\) 更新。

则变成 \(chkmn(f_x, C\pm x)\) 的形式,线段树打标记。

E https://www.luogu.com.cn/problem/P5972

题解:P5972 [PA 2019] Desant

Source: https://www.luogu.com.cn/article/vpje1cwx
Extracted: 2026-07-30T01:24:53.623Z
Extraction source: Luogu lentille-context data.article.content

给定一个 \(1\)\(n\)排列 \(a_1,a_2,\cdots ,a_n\),它有 \(2^n-1\) 个非空子序列。

请对于每个 \(k\),找到一个长度为 \(k\) 的子序列,使得这个子序列的逆序对数量最少,并输出逆序对数量最少的子序列的数量。

\(1\le k,a_i\le n\le 40\)

  • 依然是状态压缩的思想,注意到很多状态是等价的。

考虑从前往后 DP,暴力是记录 \(S\) 表示目前选择的数的集合,这样状态数仍然是 \(O(2^n)\) 级别。

但我们注意到,设 \(i\) 后面的数分别为 \(x_1, x_2, \cdots, x_k\),我们只关心 \([1, x_1), (x_1, x_2), \cdots, (x_k, n]\) 这些每一段中选了多少个数。

于是状态数不会超过每段长度 \(+1\) 的乘积,由柯西不等式可以知道一定在段长相同时取到最小值,直接看成连续的情况来分析,简单求导可以得到最小值在 \(e^{n/e}\), 由于问题是离散的于是最小值大约是 \(3^{n/3}\)

于是直接 DP 就可以了。时间复杂度是 \(O(n^2 3^{n/3})\),使用 map 或许会被卡常, 手写哈希表可以稳过。

变进制状态存储(虽然我觉得你们都会)

考虑第一位 \([0,a_1]\),第二位在 \([0,a_2]\),...,第 \(k\) 位在 \([0,a_k]\) 的向量如何存储。

\(p_i=\prod\limits_{j=1}^i (a_i+1)\),然后考虑对向量 \(b_1,\cdots ,b_k\),存储为 \(\sum\limits_{i=1}^k p_{i-1}b_i\) 即可,能空间 \(\prod(a_i+1)\) 精准存储,且不需要 map


于是这题,对于每个 \(i\),对 \(i\) 后面的 \(x_1, x_2, \cdots, x_k\) 使用变进制存储即可。转移的时候多算算状态。

ROPE

#include <ext/rope>
using namespace __gnu_pbds;
rope<int> rp;

// 1. 尾部追加(等价 push_back)
rp.push_back(10);
rp.push_back(20);

// 2. 【重点】在指定下标 pos 插入单个元素
rp.insert(pos, val);

// 下标规则:0 代表最前面插入
// 例如:rp.insert(0, 5); 在开头插入5
// 方式1:for 按下标遍历
for (int i = 0; i < rp.size(); i++) {
    cout << rp[i] << " ";
}

// 方式2:迭代器遍历
for (auto x : rp) cout << x << " ";
rp.erase(pos, len);  // 从pos开始删除len个元素
rp.replace(pos, len, new_rope); // 区间替换
rp.substr(pos, len); // 截取子串
rp.clear();
rp.size();
rp.empty();

P9965 [THUPC 2024 初赛] 转化

小 E 有 \(n\) 种颜色的球,其中第 \(i\) 种有 \(a_i\) 个。有两类工具,第一类可以把一个指定颜色的球变成一个任意颜色的球;第二类可以把一个指定颜色的球变成两个这种颜色的球。一个变化之后的球也可以通过工具产生新的变化。关于第 \(i\) 种颜色的第一类工具有 \(b_i\) 个,第二类工具有 \(c_i\) 个。小 E 想知道,如果每一工具最多只能使用一次,那么对于每种颜色 \(i\),第 \(i\) 种颜色的球最后最多能有多少个。以及,小 E 最后最多能有多少个球。

做法

考虑将变化颜色操作,转化成颜色 \(x\) 的球 \(\rightarrow ?\)

可以得到若干问号,在用的时候再进行转化。

然后进行细心贪心。

P8684 [蓝桥杯 2019 省 B] 灵能传输

\((a_{i-1},a_i,a_{i+1})\leftarrow (a_{i-1}+a_i,-a_i,a_{i+1}+a_i)\)

不稳定度为 \(\max\limits_{i=1}^n\{|a_i|\}\),不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。

做法

这种奇异操作考虑寻找不变量,常见的有差分,前缀和。

转化为 \(s_0,\dots,s_n\),中间可以随意交换。

想要在其中平滑过渡。

找到 \(max,min\)

image-20260730115254811

两边都应平滑过渡,出现反复横跳的可以移到中间不劣。

\(s\) 形如:

image-20260730115409418

则在 \(s_0,s_n\) 之间的数放在中间

两边按照交替顺序放

因为这样是 \(max(s_i-s_{i+2})\) ,若存在相邻的为更小,则必然存在一个 gap 为 3 的对。

P11660 我终将成为你的倒影

函数 \(f(x)=\lfloor \frac{x+a}{b}\rfloor\)

有一个长度为 \(n\) 的序列 \(A\)\(m\) 个询问。对于每组 \(l,r,a,b\),记 \(B_i=f(A_i)\)\(\displaystyle\sum^r_{i=l+1}[B_i=B_{i-1}]\) 的值为多少

本题强制在线,每次的 \(l,r,a,b\) 均要异或上一次的答案,若这是第一次询问,则不做操作。

做法

容易从 \(b \le 500\) 入手。

对于每个 \(b,x_i,x_{i+1}\),合法的 \(a\) 是一段模意义下的区间。

有做法枚举 \(b\),对每个 \(b\),枚举 \(i\) 处理出合法的 \(a\in[l_i,r_i]\)

查询为

\[\huge \sum_{i=l}^{r-1} [a\in[l_i,r_i]] \]

可以差分。

但是没法存下每一个 \(i\) 对应的答案。

时间复杂度 \(O((n\times b +q)\log(n\times b))\)

考虑维护这个差分数组,每 s 个保存一次。

然后询问时拷贝并修改。

时间复杂度为 \(O(b\times n/s \times b + nb +qb +qs)\).

\(s = b\) 达到平衡。

CF1442D Sum

题目描述

给定 \(n\) 个单调不减的非负数数组。

Vasya 重复进行 \(k\) 次如下操作:

  • 选择一个非空数组。
  • 将所选数组的第一个元素放进口袋。
  • 从所选数组中移除第一个元素。

Vasya 想要最大化他口袋中元素的和。

做法

贪心发现只有一个数组不全选。

变成我们想知道 全局01背包删掉 \(i\) 的背包,然后把他与 \(s_i\) 合并。

需要注意到插入是简单的,删除是困难的,但背包撤销也是简单的。

可以想到线段树分治。做完了。

CF1413E Solo mid Oracle

题目描述

Meka-Naruto 玩一个电脑游戏。他的角色拥有如下技能:对一个敌方英雄造成 \(a\) 点瞬间伤害,然后在技能释放后每秒结束时为该敌人恢复 \(b\) 点生命值,持续 \(c\) 秒,从技能释放后第 \(1\) 秒开始。也就是说,如果在时间 \(t\) 释放技能,敌人的生命值会在 \(t\) 时刻减少 \(a\),然后在 \(t+1\)\(t+2\)、…、\(t+c\) 时刻分别因该技能各恢复 \(b\) 点生命。

该技能有 \(d\) 秒的冷却时间,即如果 Meka-Naruto 在时刻 \(t\) 使用技能,则下次可以在 \(t+d\) 时刻再次使用。请注意,他只能在整数时刻使用技能,因此所有对敌人生命值的改变也都只会发生在整数时刻。

不同技能释放的效果可以叠加;也就是说,当前有 \(k\) 个技能效果作用在敌人身上时,敌人本秒会恢复 \(k \cdot b\) 点生命。此外,如果同一时刻有多个生命值变化发生,这些变化会同时结算。

现在 Meka-Naruto 想知道,如果他每次技能冷却好就立刻释放(即每隔 \(d\) 秒释放一次技能),他是否能仅靠这个技能杀死敌人。敌人在生命值降到 \(0\) 或以下时被击杀。假设敌人的生命值只会受到 Meka-Naruto 技能的影响。请问敌人最多能有多少生命值,Meka-Naruto 仍然能够击杀他?

做法

*greedy *math

考虑 -1 的情况,则当 \(b \times c > a\),则一次进攻的贡献为正,必定击杀。

\(b \times c \le a\),则我们考虑调整法,若答案在某个位置,而这个位置前有完整的一次进攻,我们把他删掉也不劣。

故只会在第一次进攻之前决定。

C.蜗蜗的数字迷宫

https://newoj.daimayuan.top/p/5483?tid=6a6b805dab41ab6cce69e029

图上走 k 步字典序最小。

做法

最优走法形成一棵基环树,考虑直接正着做,维护当前最优点, 走 \(3n\) 步。

得到一个权值序列,又因为混循环在 \(n\) 步就结束了,接下来必定进入循环,故对后 \(2n\) 找出循环节即可。

day2 #D. 沙海奥秘

对于区间在 \([l,r]\) 合法的不仅可以考虑成矩形,还可以考虑成对于每个 \(i \in [l,r-1]\)\([i,i + 1]\) 进行覆盖表示都是合法的。

然后就可以维护 \(0\) 的连续段计算答案。

Trick:树上偏序

经典式子:排列拍到树上,小根堆数目为 \((\prod siz_x)^{-1 } \times n!\)

CF1278F Cards

题目描述

考虑如下实验。有一副共 \(m\) 张牌的牌堆,且恰好有一张是小丑牌。你将进行 \(n\) 次如下操作:将牌堆洗牌,从牌堆顶端抽出一张牌,查看后再放回牌堆。

\(x\) 表示在本次实验中你抽到小丑牌的次数。假设每次洗牌后,所有 \(m!\) 种牌的排列都是等概率的,求 \(x^k\) 的期望值是多少?请将答案对 \(998244353\) 取模后输出。

做法

考虑 \(x^k\) 组合意义,\(x\) 的期望就是若干个 01 变量 \(1\) 的个数的期望。

考虑怎么变成 \(x^K\)

那我们就选一个可重复的 \(k\) 元组,元素 \(\in [1,n]\),变成这个 \(k\) 元组都得是 \(1\)\(k\) 元组期望个数。

统计答案?只跟 \(k\) 元组出现元素个数 \(c\) 有关。$f_{k,c} $ dp即可。

trick本质不同

怎么统计操作后互不相同的 01串?

可以考虑变化的 \([L,R]\) 区间。

CF1548C The Three Little Pigs 多项式

\[f_x =\large \sum_i \binom{3i}{x} \]

我们知道有

\[\large \binom{n}{k} = [x^k](1+x)^n \]

考虑用这个生成函数优化。

变成了一个等比数列求和。

得到了一个多项式除以另一个,直接大除法。

CF1765C Card Guessing

题目描述

好题!

考虑一副扑克牌。每张牌有 \(4\) 种花色,每种花色恰好有 \(n\) 张牌——因此,这副牌共有 \(4n\) 张牌。牌堆经过随机洗牌后,$ (4n)! $ 种可能的排列顺序每种都等概率出现。记 \(c_i\) 为牌堆中的第 \(i\) 张牌(从上到下编号)。

Monocarp 开始依次从牌堆顶抽牌。在抽每一张牌之前,他会尝试猜测这张牌的花色。Monocarp 会记住最近 \(k\) 张牌的花色,他的猜测是:在最近抽出的 \(k\) 张牌中出现次数最少的花色。也就是说,在抽第 \(i\) 张牌时,Monocarp 会猜测其花色为 \(c_{i-k}, c_{i-k+1}, \dots, c_{i-1}\)\(k\) 张牌(如果 \(i \le k\),则他会考虑所有已经抽出的牌,即 \(c_1, c_2, \dots, c_{i-1}\))中出现次数最少的花色。如果有多个花色出现次数同为最少,他会等概率地随机选择其中之一作为猜测。

做出猜测后,Monocarp 抽出一张牌,并将其花色与自己的猜测进行比较。如果猜对了,则本次猜测正确;否则为错误。

你的任务是计算,Monocarp 抽完全部 \(4n\) 张牌后,猜对的次数的期望值。

做法

我们首先要知道一件事情,其中 \(k\) 牌中四种花色分别是 \(c_1,c_2,c_3,c_4\) 的概率是什么?

\(k\) 张牌中每种集合的可能性一样。

\(P = \frac{\binom{n}{c_1}\times \cdots \binom{n}{c_4}}{\binom{4n}{k}}\).

期望线性性一下。

假设知道了 \(c_1 \dots c_4\),我们猜对的概率是啥?不妨设猜下标最小的。

相当于我们先决定了这 \(k\) 个,再去了解其他的。

题目说随机选取,实际上选哪个最小的都一样。

猜对的 \(P' = \frac{n-min}{4n-k}\).

\(ans_k = \sum_{c_1+c_2+c_3+c_4=k} P \times P'\).

以上自然。

考虑 dp。

列下需要知道的状态:\(min,sum\) 再在里面维护一下一坨组合数状物做完。

P14368 [JOISC 2018] 修行 / Asceticism

求排列数,恰好有 \(k-1\) 个相邻逆序对。

做法

可以考虑二项式反演。

看成 \(n - 1\) 个限制 \(>/<\)

\(g_k\) 表示恰好满足其中 \(k\) 个限制,而 \(f_k\) 表示钦定满足 \(k\) 个是前大于后的。

容易列出二项式反演。

\(f_k = \sum_i g_i \binom{i}{k}\)

\(g_k = \sum_i f_i \binom{i}{k} (-1)^{i-k}\).

咋求 \(f(j)\).

假设我们填了 \(j\)\(>\).

每填一个少一个块,因此最后会形成 \(n-j\) 个块,每个块都会下降。

而且要求每个块都要非空。

我们可以自然地相当要填 \(n \rightarrow 1\),每次都可以放进一个块的开头。

但是要求非空。

容斥一下。\((?-kong) \times(?-kong)\times \dots \times\).

枚举空的个数。

\(f(j) = \sum_{k=0}^{n-j} (-1)^k \times \binom{n-j}{k} \times (n-j-k)^n\).

\[\large g_t = \sum_j \binom{j}{t} (-1)^{j-t} \sum_{k=0}^{n-j} (-1)^k \times \binom{n-j}{k} \times (n-j-k)^n \]

\[\large g_t = \sum_j \binom{j}{t} (-1)^{j-t} \sum_{k=0}^{n-j} (-1)^k \times \binom{n-j}{k} \times (n-j-k)^n \]

\[\large g_t = \sum_j \binom{j}{t} (-1)^{j-t} \sum_{k=0}^{n-j} (-1)^k \times \binom{n-j}{k} \times (n-j-k)^n \]

不妨令 $s = j +k $

\[\sum_{s=0}^{n} (-1)^{s-t} (n-s)^n \sum_{j\le s}\binom{j}{t} \binom{n-j}{n-s} \]

后面是经典的式子。

化为

\[g(t)=\large \sum_{s=0}^{n} (-1)^{s-t} (n-s)^n \binom{n+1}{s-t} \]

做完。

day4 #D. 蜗蜗的全一数

问一个长度 \(5\times 10^5\)\(N\),因数中最长的全一数。

部分分-40pts

\(|N|^3\) 直接写个高精度除法。

部分分-60pts

因为除数很特殊,考虑

做法

由结论

\[a\mod b=c \Leftrightarrow ak \mod bk = ck (k>0). \]

考虑 \(N\times 9\).

我们每 \(k\) 位可以 划开,然后发现里面可以模掉一个999999.

所以变成每 \(k\) 位和是 999999 的倍数。

\(S_k = 9999\dots9 \times C\).

注意到这里的 \(C \le n/k\),因为每 \(k\) 位小于 99999。

现在发现 \(C\) 的范围是小到可以枚举的。

我们考虑怎么判定这件事情。

哈希。

做完了。

P17167 [CEOI 2026] Towers day4T5

进行部分分的学

题目描述

一条直线上有 \(n\) 台计算机和 \(m\) 座塔,它们的位置两两不同。你需要使用线缆将计算机两两配对,使每根线缆从某台计算机出发,经过若干座塔,最终到达另一台计算机。线缆可以按任意顺序经过任意一些塔,不必只经过两台计算机之间的塔。线缆经过某座塔旁边时可以跳过它而不访问。线缆也可以不访问任何塔,直接连接两台计算机,但不能将一台计算机连接到自身。计算机数量为偶数。

\(a\)\(b\) 是两台计算机的位置,\(x_1,\ldots,x_k\) 是线缆所访问的塔的位置。该线缆的长度为 \(|a-x_1|+|x_1-x_2|+\cdots+|x_{k-1}-x_k|+|x_k-b|\)。对于一根线缆,将其得分定义为 \(f\cdot u-l\),其中 \(l\) 是线缆长度,\(f\) 是某个固定常数,\(u\)\(x_1,\ldots,x_k\) 中线缆访问过的不同塔的数量。多根线缆可以访问同一座塔,而且该塔会分别计入每根线缆的得分。

你需要将所有计算机两两配对,并求线缆得分总和的最大可能值。也就是说,每台计算机都必须恰好属于一个配对;等价地,每台计算机都必须恰好连接一根线缆。

输入格式

第一行包含测试用例数量 \(T\),随后依次给出各个测试用例。每个测试用例包含三行。第一行包含三个整数 \(n\)\(m\)\(f\),分别表示计算机数量、塔的数量以及常数 \(f\)。第二行包含 \(n\) 个整数 \(a_1,a_2,\ldots,a_n\),表示各台计算机的位置。第三行包含 \(m\) 个整数 \(b_1,b_2,\ldots,b_m\),表示各座塔的位置。

输出格式

输出 \(T\) 个整数,每个整数单独占一行,依次表示每个测试用例中线缆得分总和的最大可能值。

输入输出样例 #1

输入 #1

4
2 1 100
1 10
11
4 1 10
2 4 6 8
20
4 1 10
2 4 6 8
5
6 3 10
2 13 4 8 6 10
5 1 9

输出 #1

89
-4
12
51

说明/提示

限制条件

分别以 \(N\)\(M\) 表示所有测试用例中 \(n\)\(m\) 的总和。

  • \(1\le T\le 10^4\)
  • \(1\le N,M\le 2\cdot 10^5\)
  • \(0\le f\le 10^9\)
  • \(n\) 为偶数
  • \(1\le a_i,b_i\le 10^9\)
  • 在每个单独的测试用例内,所有计算机和塔的位置两两不同。

子任务

  • 子任务 \(1\)\(5\) 分):\(N\le 5000\)\(m=1\)
  • 子任务 \(2\)\(10\) 分):\(T\le 20\)\(n\le 10\)\(m\le 100\)
  • 子任务 \(3\)\(27\) 分):\(N,M\le 5000\)
  • 子任务 \(4\)\(21\) 分):\(N\le 5000\)
  • 子任务 \(5\)\(37\) 分):无额外限制。

翻译由 ChatGPT-5.6 完成

task 1

可以去猜测贪心并证明。

task 2

强暴枚举 可

做法

这种配对问题两两组合不好下手。

我们应该思考怎么样能拆代价能让一个配对 \((x,y)\) 两两比较独立。

容易发现策略一定是 \(A\) 到一个 \(l_i\) 塔一直走到 \(r_i\) 再到 \(B\) 完成配对

\(A,B\) 配对代价 \(f\cdot u-l\) 可以先拆出来个 \(-|AB| = A-B\).

\(cnt(A,B)\) 是中间的星星数量。

还能再拆出来个 \(cnt(A,B)\times f = sum_Bf - sum_Af\).

除此之外还有分别两边的贡献。

容易预处理出来每个点往左往右的最优决策 \(L_i,R_i\)。可以递推。

可以在 \(O(1)\) 处理出来两两的代价 \(f(x,y)\)

这种配对问题容易写出一个 $O(n^2) $ 的匹配 dp。

因为第 \(i\) 台电脑具体跟谁匹配并不关键,关键在于在左还是在右被匹配的。

dp:前 \(i\) 位匹配池中有 \(j\) 个,转移考虑 \(i\) 去匹配还是去匹配池中以及 \([i-1,i]\) 区间中需要加上的贡献。

优化

其实可以直接看成括号匹配了,反悔贪心即可。

相当于求序列上最大权值合法括号串,左括号权值为 \(L_i\),右括号权值为 \(R_i\)

初始看成都左括号,每次加入都看成是右括号

然后我们看下是不是负了,就把其中一个右括号改成左即可。

括号合法性转成了「前缀内最多选 ⌊i/2⌋ 个元素」的问题

每次加入一个候选右括号,如果超过容量,就反悔删掉收益最低的选择。

// 维护右括号集合
//初始钦定为左括号
//序列上最大权值合法括号串,左括号权值为 $L_i$,右括号权值为 $R_i$。
priority_queue<ll,vector<ll>,greater<ll> > q;
int ct = 0 ;
for (int i = 1;i <= n + m;i ++ )
{
    if (a[i].op == 0) continue;
    ++ct;
    q.push(D[i]);ans += D[i]; 
    if (sz(q) > ct/2)
    ans-=q.top(),q.pop();
}

而你多了就弹,是一直顶着这个界限的。

最后就一定是合法的括号。

P11749 「TPOI-1C」Standard Problem.

形式化的,给定字符串 \(s\) 和整数 \(k\),求 \(s^k\)(字符串 \(s\) 拼接 \(k\) 次)有多少回文子串(位置不同即算作不同),模 \(998244353\)

思路

我们考虑 \(s+s+s\),那么左右两边的可以先算出来,再算中间的。

容易发现若能延伸到边界,那么对应位置就能无限延伸。

不能的话直接算。

但是无限延伸的在中间怎么计算贡献呢?

类似一个 \(min(L,R)\) 的式子比较恶心。

P11444 [Code+#6] 祖玛

题目背景

搬运自 Code+ 第 6 次网络赛


小粽还是一个小粽子的时候,特别喜欢玩一款叫作祖玛的游戏。现在,小粽长大了。为了纪念她的童年时光,她开发了一款新型祖玛游戏,并为你准备了一个问题。

题目描述

小粽的祖玛游戏的游戏规则可以抽象为如下模型:

初始时,有一段长度为 \(n\) 的正整数序列 \(a_1,a_2,\dots,a_n\)。游戏过程中,小粽会对这个序列进行一系列规则相同的操作:从序列中选取连续且相同的一段数,设这段数的长度为 \(X\),如果这些数的值都相等,那么小粽可以把这些数从序列中删除,并将序列从删除的位置接起来,例如,对于序列 2 3 3 3 1,可以删除中间的 3 3 3,得到 2 1

不过,小粽觉得只是这样太简单了,于是她选择了两个数 \(X_{min},X_{max}\),并且要求每次删除的那段数的长度 \(X\) 要满足 \(X_{min}\le X\le X_{max}\)

显然小粽能进行的操作次数是有限的,甚至她有可能不能把整个序列删除完。现在,小粽想要知道,她每次删除的数的长度的平方和是多少。即,设 \(X_i\) 为第 \(i\) 次删除的数的长度,最大化 \(\sum X_i^2\)

image-20260807170405279

如何想到这个 dp 的构造?

可以先套路地设出一个 \(dp[l][r]\) 表示把这个区间删掉。

注意到当 k≥2 时,我们在转移 fl,r 时选取的任意一个长为 k 的子序列,都可以看作转移 fp2,r 时选取的一个长度为 k−1 的子序列加上 a_l

CF1842G Tenzing and Random Operations

题目描述

又一道随机问题。

Tenzing 有一个长度为 \(n\) 的数组 \(a\) 和一个整数 \(v\)

Tenzing 将进行 \(m\) 次如下操作:

  1. 每次等概率随机选择一个整数 \(i\),满足 \(1 \leq i \leq n\)
  2. 对所有满足 \(i \leq j \leq n\)\(j\),将 \(a_j\) 赋值为 \(a_j + v\)

Tenzing 想知道,经过 \(m\) 次操作后,\(\prod_{i=1}^n a_i\) 的期望值是多少,结果对 \(10^9+7\) 取模。

形式化地,设 \(M = 10^9+7\)。可以证明答案可以表示为最简分数 \(\frac{p}{q}\),其中 \(p\)\(q\) 是整数且 \(q \not\equiv 0 \pmod{M}\)。请输出等于 \(p \cdot q^{-1} \bmod M\) 的整数。换句话说,输出一个整数 \(x\),满足 \(0 \le x < M\)\(x \cdot q \equiv p \pmod{M}\)

做法

考虑拆期望。

\(\prod (a_i + v_1 + v_2 + v_3 \dots)\)

如果 \(m\) 很小,我们显然可以把前面选了几个出来记录下了。

但是这里 \(m\) 很大。

鸽。

P4448 [AHOI2018初中组] 球球的排列

\(n \le 300\) 个球,有颜色,问排列数使得相邻不相同。

做法

按颜色排序并分组

考虑一个个插入进排列。

当前颜色是可以相邻的,只不过需要被后面的球隔开。

因此需要记录一个 \(neighbor\) 表示相邻的组数。

同时一个颜色的球插入进去是可以跟前面的球一起增加 \(neighbor\) 的。

于是我们还有记录一个当前组的球被插到了几个空中。

然后我们就可以愉快 dp 了。

容斥做法

、。

Day 5 模拟赛

堪比 noip2025 的一场爆炸性的比赛。

T1 P7015 [CERC2013] Crane

\(n\le 1e4\) 个箱子等着装上船。箱子的编号是 \(a_1,a_2,\cdots,a_n\)。你的工作是通过若干次交换,将它们从小到大排列。你每次可以选择一个区间,将它的前半部分与后半部分交换,两半内部的顺序保持不变。你最多可以交换 \(531441\) 次。

做法

考虑怎么交换两个数,然后按照 \(1\to n\) 的顺序归位。

可以令 \(l=i\)\(r\) 选择合适的位置,可以发生 swap。

如果不行可以考虑换过来再 swap。

T2

\(n\) 个磁石和铁块,可以选择启动一个磁石,然后他会跟最近的铁块消失。

问使得铁块移动距离最小。

做法

看上去是一个类似匹配的问题。

先考虑下界。

会有一些不优的匹配,比如交叉:OXXO 13匹配就不如12匹配

因此有一种比较直接的想法。

排成一排,括号匹配。

但是这玩意就不好做,你很可能面临一些调整。

启示

在这种先求下界,在寻求是否有可行构造的题目中

不好做可能是因为你求下界给出的构造不够好。

考虑通过调整法调整出一个比较好的构造。

正解

考虑调整,我们知道包含可以化成交叉。

于是一点一点调整,我们调出了 \((a_i,b_i)\) 匹配。

这个看起来好做了点。

那我们从头构造。(磁石 X,铁块 O

假设 \(X_1\)\(O_1\) 前,那我们直接吸附,问题递归。

反之,我们现在不一定能操作,因为后面可能很近。

继续看。

考虑 \(O_2\)\(X_1\) 更近,那我们现在还是动不了,继续往后看。

反之,直接操作 \(1\)

当我们存在两个 \(X\) 相邻的时候,我们有操作:哪边近操作哪边。

这样我们用一个栈便能模拟。

code

T3

好题。

问长为 \(n\) 的01环,长度为 \(k\) 的滑动窗口有多少本质不同的和数组。

序列的第 s_i 个值就是以第 i 个符文为起点、顺时针连续 k 个的和。

做法

这种滑动窗口东西,还是得考虑相邻两个窗口能告诉我们什么。

在原环上不太好想,因为我们很难知道怎么会重复。

我们在 \(s\) 上考虑 \(s_i \rightarrow s_{i+1}\) 的变化。

\(s_i \ne s_{i + 1}\),即 \(a_i - a_{i+k} = s_i-s_{i+1}\),因此我们可以知道他们具体的值。

又因为在确定的 \(s\) 下,\(a_j\)\(a_{j+k}\) 的差值是知道的。

我们就能知道 \(\forall a_{i +pk}\) 的值。这个也就是 ${a_{j} | j = i\mod g } $ ,其中 \(g =\gcd(n,k)\).

反之,若对于剩余系 \(j\) 都不存在 \(s_i \ne s_{i + 1}\),说明啥?

这个剩余系的 \(a\) 全部相等。

性质就研究到这里。

这里有个很妙的事情,我们现在总不能直接去填 \(s\),你完全不知道合不合法。

我们令 \(f(a) = s\),表示 \(a\) 做一遍滑窗得到的东西。

那我们要对什么计数?\(f(a)=s\)\(a\) 的等价类。

换句话说,把那些 \(f(a)=s\)\(a\) 放在一个类中,我们去数类的个数。

\(a\) 用剩余系分类,再分为全相等剩余系集合 \(A\),与不全相等的剩余系集合 \(B\).

\(B\) 中任意一个系由于不全 \(0\),只要我们任意修改 \(a_i\)

\(a_i - a_{i+k} = s_i-s_{i+1}\)\(s\) 中必有相邻差值的变化,于是我们得到重要的结论:

\[\huge \text{一个剩余系内不全是1的填法的f(a)互不相同} \]

自然地,考虑对于 \(A\) 来说,什么情况下会重复?

一个系全填 1,刚好,\(\forall i,s_i \leftarrow s_i + k/g\).

即对于 \(A\) ,按照全填 \(1\) 的数量划分等价类。

于是,我们枚举 \(t\) 表示 \(A\) 集合的大小。则有 \(t + 1\) 种等价类。

开始计数。\(g=gcd(n,k)\).

\[\sum _{t = 0}^{g}(t + 1)\times \binom{g}{t} \times (2^{n/g} -2)^{g-t} \]

\[\sum_{t=0}^g \binom{g}{t}(2^{n/g}-2)^{g-t}+\sum_{t=1}^g t\binom{g}{t}(2^{n/g}-2)^{g-t} \]

由二项式定理及吸收公式 \(\binom{a}{b} \binom{b}{c} = \binom{a}{c}\binom{a-b}{b-c}\)

\[(2^{n/g}-1)^g+g \sum_{t=1}^g \binom{g-1}{t-1}(2^{n/g}-2)^{g-t} \]

\[(2^{n/g}-1)^g+g \sum_{i=0}^{g-1} \binom{g-1}{i}(2^{n/g}-2)^{g-i-1}1^{i} \]

\[(2^{n/g}-1)^g+g(2^{n/g}-1)^{g-1} \]

做完了.

番外

又想到一种做法。考虑环上差分数组 \(c_i\),这个与 \(s_i\) 呈单射关系。

我们每设一个 \(1\),就相当于给 \(s_{[i,i+k]}\) \(+1\).

转化为差分有 \(c_i \leftarrow c_i + 1,c_{i+k} \leftarrow c_{i+k} - 1\).

我们连边 \((i,i+k)\),则每个剩余系形成一个置换环。

问题转化为

在这个环上,每条边 \(i \rightarrow i +k\),可以选择并移动一个 \(1\)

容易发现只有全选和全不选是重复的,后面在考虑等价类时,又得回到数组 \(a\)

跟前面的做法是一样的。

T4

长剖优化 dp。https://www.luogu.com.cn/problem/P9655

指针写法

int *p; // 指向int的指针
int a = 10;
int *p = &a; //指向 a 的地址
//*p 表示解引用
cout << *p; // 输出真实数值
//修改指针的数值
*p = 1145;

//指针and数组
int a[5] = {1,2,3,34,4,5};
// a -> &a[0]
int *p = a; // int *p = &a[0]
p[0]=1
p[1]=2
p[2]=3
int a[10];
int *p=a+5;
p[-1] // 可以访问负数下标
// *(p + 2) == a[3]

int *p[10]; // 指针数组 10个指针
int (*p)[10]; //指向一个长度10的数组

int *p=new int[100];
p[0]=5;
p[99]=10;



链表写法

考虑换上重儿子的信息,再去合并轻儿子,最后合并上 \(x\) 的信息。

用链表很好维护。

list<int> dp[N];
int down[N],son[N];
int tag[N];
int ans ;
void dfs(int x,int fa)
{
	down[x] = 1;
	if (x != 1 && sz(v[x]) == 1)
	{
		dp[x].pb(-INF);
		if (c[x] == -1) dp[x].pb(1);
		else dp[x].pb(-INF);
		return ;
	}
	for (auto y : v[x])
	{
		if( y == fa) continue;
		dfs(y,x);
		if (down[y] + 1 > down[x]) down[x] = down[y] + 1,son[x] = y;
	}
	swap(dp[x],dp[son[x]]);
	tag[x] = tag[son[x]];
	for (auto y : v[x])
	{
		if ( y== fa || y == son[x]) continue;
		auto itx = dp[x].begin();
		for (auto it : dp[y])
		{
			it += tag[y];
			if (it > 0) 
			{
				if (*itx+tag[x] < 0) *itx = -tag[x];
				(*itx) += it ;
			}
			itx = next(itx);
		}
	}
	if (c[x] == -1)
	{//dp[x][0]=0
		if (*dp[x].begin() + tag[x] <  0)
			*dp[x].begin() = -tag[x];
	}
	if (c[x] == 1){
		dp[x].pop_front();
	}
	else dp[x].push_front(-INF);
	while (sz(dp[x]) < down[x]) dp[x].pb(-INF);
	tag[x] ++ ;
	ans = max(ans,*dp[x].begin() + tag[x]);
}

T5

延迟决定

图论

百合

无向图,加上一些 \(x\to y,a_{popc(x\oplus y)}\) 的边,最短路。

异或想到按位考虑。

有一个递推的关系。

可以设 \(f_{x,i,j}\) 表示当前是 \(x\),可以在前 \(i\) 位免费使用 \(j\) 次取反。

转移考虑 \(i\) 是否取反,边权为 \(0\)

\(f_{x,0,0} \rightarrow f_{x,17,j} ,a_j\)

边数太多无法通过

trick:快速松弛

当一个图大量的边边权为 \(0\),可以考虑在最短路dij过程中。

容易发现只有 \(f_{x,0,0}\) 的点是直接有用的,因为 \(f_{x,i,j}\) 一定要经过若干条到达 \(f_{y,0,0}\).

所以我们最短路时只记录 \(dis_x\)

加入一个点时,直接跑 bfs 将其 \(0\) 联通块全部加入松弛,打上 vis 标记。

这样边数大幅减少可以通过。

为什么有效果?在 0 权块中,当取当前 dis 最小黑点出来时,顺着 0 权更新的所有点都是最小黑点。

而我们在传统算法中,需要把这些点全部加入,非常浪费时间。

我们可以一遍bfs 快速将这些点全部拿去松弛。

AT_agc056_c [AGC056C] 01 Balanced

考虑构造一个由 01 组成的长度为 $ N $ 的字符串 $ s $。其中 $ s $ 需要满足 $ M $ 个条件。第 $ i $ 个条件由整数 $ L_i, R_i \((\) 1 \leq L_i < R_i \leq N $)表示,这意味着在 $ s $ 的第 $ L_i $ 个字符到第 $ R_i $ 个字符之间,包含的 01 的数量必须相等。

请在所有满足条件的 $ s $ 中找出字典序最小的那个。可以证明,在问题的约束下,满足条件的 $ s $ 一定存在。

深刻的差分约束题。

做法

这个题最重要的是考虑如何合理建边使得图中不出现负权边导致需要跑 spfa。

首先,字典序最小,说明必须跑最长路。

因为最长路可以同时得到每个变量的最小可能解。

为啥?考虑一个最长路限制 \(f_y \ge f_x + w\).

则对于所有的限制整合起来最终应该会有 \(f_y \ge W\).

而我们跑最长路的过程中刚好就取到了这个 \(W\)。得到了最小解。

那么我们就知道了,跑最长路这张图肯定不能出现正权边。

考虑建图。

\(0,1\) 个数相等,容易令 \(0 \to -1\)

对于 \((l,r]\),有 \(s_r = s_l\)

还有 \(s_i - s_{i -1} = \pm 1\)

尝试化成 \(s_i- s_{i-1} \le 1\)\(s_{i-1} - s_i \le 1\)

发现确实没有正权边。

最后只剩一个限制 \(s_r\) 不等于 \(s_l\),由奇偶性,这个限制也自然满足了。

最长路可以边权取反跑最短路,我们就做完了。

图论2 B 港口设施

这道题是 JOISC 2017 的经典题目 Port Facility(港口设施)

  • \(N\) 个货物。
  • 有 2 个栈(Stack)。
  • 希望第 \(i\) 个货物在时刻 \(A_i\) 被堆叠在其中某一个栈的栈顶,并在时刻 \(B_i\) 从该栈的栈顶被取出。
  • 问满足条件的堆叠方案共有多少种?
  • \(N \le 10^6\)

做法

什么时候两个货物冲突?区间相交。

考虑相交区间连边,做二分图判定,\(ans=2^{\# \text{联通块}}\),得到了一个 \(O(n^2)\) 的做法。

对联通块分开考虑。

点数 \(O(N)\),边数 \(O(N^2)\),如果检查所有的边就会 TLE.

检查所有的边是没有必要的,指向已访问过顶点的边根本不需要查看。

因此我们希望在访问一个节点后,给它连的边都删掉。

但是,如果一条一条地删除边,复杂度仍为 \(O(N^2)\)。需要批量删除边。

考虑与每个区间相邻的区间是怎样的区间?

设当前区间为 \([L, R]\)

  • \(L\) 之前开始、并在 \([L, R]\) 之间结束的区间;
  • \([L, R]\) 之间开始、并在 \(R\) 之后结束的区间。
  • 两者对称,只需考虑其中一种情况。

考虑线段树,因为区间端点互不相同。

\(C_{R_i} = L_i\).

则在 \([L_i,R_i]\) 里查一个最小值容易找到相邻区间。

bfs 一旦访问过就直接删掉,避免重复染色,设为 INF 即可。

另一边也是一样。

最后再用栈来检验合法性。

F - Square Constraints

  • For each i (0≤i≤2N−1), N2≤i2+P**i2≤(2N)2 holds.
  • 问可能排列数

做法

将限制化成二维 \((i,p_i)\),发现在一个1/4圆环中,有 \([l_i,r_i]\) 然后容斥 + dp。

A - Round 2 单图

有趣的图论数数。

问有标号单图数。

单图定义为不存在其他简单有向图与他联通情况相同。

做法

容易发现不能有长为 3 的链,但是可以有独立 2 元环。

于是考虑数数。

这种图数数问题一定要考虑怎么样能简化问题,使得问题可以不重不漏的计数。

首先可以想到枚举二元环个数。

然后你看剩下的东西。

一个点一定是全出边或全入边。

这个咋数呢?

我们不妨把他们按照有出边与没有出边分成 \(AB\) 类。

一定是 \(A \to B\)

那我们就容易想到枚举 \(A\) 的数量然后钦定 \(A\) 一定要有边,不然就成 \(B\) 了。

然后就做完了。

summer holiday record 2

一些杂记

哈希冲突概率

这是一个关于生日悖论的事情。

\(p(n,d)\) 表示 \(n\) 个串,\(hash\) 空间为 \(d\) 冲突概率。

\[p(n,d) = 1-1\cdot(1-\frac{1}{d})\cdot(1-\frac{2}{d})\cdots \]

\[\overline{p}(n,d) \approx 1-\exp\left(-\frac{1}{d}\right) \cdot \exp\left(-\frac{2}{d}\right) \cdots \exp\left(-\frac{n-1}{d}\right) \]

化简

\[\exp(x) = \sum_{k=0}^{\infty} \frac{x^k}{k!} = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \frac{x^4}{4!} + \cdots \]

经典的一阶近似公式:

\[\exp(x) \approx 1 + x \quad (\vert{}x\vert{} \ll 1) \]

\[\begin{aligned} \overline{p}(n,d) &\approx \exp\left( -\frac{1}{d} -\frac{2}{d} -\cdots -\frac{n-1}{d} \right)\\ &= \exp\left( -\frac{n(n-1)}{2d} \right) \end{aligned} \]

则 Hash 冲突的概率为:

\[\boxed{ p(n,d) \approx 1-\exp\left( -\frac{n(n-1)}{2d} \right) } \]

树的重心

  • 重心是 \(\max{siz_{son}}\) 最小的点。
  • 充要:\(\forall siz_{son} \le \lfloor\frac{n}{2} \rfloor\)
  • 最多两个,必定相邻(相邻时断开边两部分均为 \(\frac{n}{2}\).
  • 最小 \(\sum_y dis[x][y]\) 的点 \(x\) 是重心,相邻时则相等
  • 两树合并重心在重心之间路径上

P7669 [JOI 2018 Final] 月票购买 / Commuter Pass

题目描述

JOI 君住在一个有 \(N\) 个车站的城市。车站编号为 \(1\)\(N\)。编号为 \(1\)\(M\) 的有 \(M\) 条铁路。铁路 \(i\)\(1 \leq i \leq M\))双向连接 \(A_i\) 站和 \(B_i\) 站,票价为 \(C_i\) 日元。
JOI 君住在 \(S\) 站附近,去 \(T\) 站附近的 IOI 高中。他打算买一张连接这两个站的通勤票。当他购买通勤票时,他需要选择一条成本最低的 \(S\) 站和 \(T\) 站之间的路线。使用此通勤票,他可以沿任何方向乘坐所选路线中包含的任何铁路,而无需额外费用。
JOI 君经常去 \(U\) 站和 \(V\) 站附近的书店。因此,他想买一张通勤票,这样从 \(U\) 站到 \(V\) 站的费用可以降到最低。
当他从 \(U\) 站移动到 \(V\) 站时,他首先选择了一条从 \(U\) 站到 \(V\) 站的路线,那么他需要支付的车费是:

  • \(0\) 日元:如果铁路 \(i\) 包含在他购买的通勤票时选择的路线中,或者,
  • \(C_i\) 日元:如果铁路 \(i\) 不包含在他购买通勤票时选择的路线中。

上述票价的总和就是从 \(U\) 站到 \(V\) 站的成本。
他想知道如果他在购买通勤票时选择合适的路线,从 \(U\) 站到 \(V\) 站的最低成本。

做法

先找出来最短路径图是自然的。

但是这些路径并不是都能设为 0。

考虑这个最短路径图对于 \(UV\) 路径的贡献。

发现一定是一条路径。

若不是,则中间可能有走其他边,那还不如走最短路径图上的边。

这样我们很容易算出答案的式子。

\(G\) 是最短路径图。

\(\min _{x,y \in G} d_{u,x} + d_{y,v}\)。要求 \(x,y\)\(G\) 上存在一条最短路径经过他们。

这个呢,给 \(G\) 定向一下, 随便拓扑排序一下就行了。

P13583 [NWRRC 2023] Colorful Village

题目描述

彩色村庄是一个著名的旅游胜地。村里有 \(2n\) 座房屋,编号从 \(1\)\(2n\)。每座房屋都有 \(n\) 种颜色中的一种,颜色编号从 \(1\)\(n\)。巧合的是,每种颜色恰好有两座房屋被涂成该颜色。

彩色村庄有 \(2n-1\) 条双向道路。每条道路连接两座不同的房屋,并且通过这些道路可以从任意一座房屋到达任意另一座房屋。

Catherine 正计划前往彩色村庄旅游。由于时间有限,她希望选择一个包含 \(n\) 座房屋的集合 \(S\) 进行参观,且每种颜色恰好选一座房屋。然而,由于 Catherine 还需要在房屋之间移动,所选择的房屋集合必须是连通的。换句话说,集合 \(S\) 中的任意两座房屋都可以仅通过集合 \(S\) 内的房屋和道路互相到达。

请帮助 Catherine 找到一个满足条件的连通房屋集合 \(S\),包含 \(n\) 座房屋且每种颜色各选一座,或者报告不存在这样的集合。

做法

首先要想到 2-sat。

然后想想联通块。

联通块在任意的情况下是不好做的。

什么情况下好做?

我们固定一个根,那么有限制 \(x\) 选了 \(fa\) 必选。

做完了。

模拟赛 day 6

拉完了

T1

蜗蜗制作了一个圆形风铃,圆环上均匀分布着 2n2n 个挂钩,按顺时针依次编号为 00 到 2n−12n−1。他有 nn 条彩带,每条彩带的两端分别系在两个挂钩上。初始时,每个挂钩上恰好系着一条彩带的一端(每条彩带连接两个不同的挂钩,且所有的挂钩都被占用)。

蜗蜗觉得任意两条彩带都平行的时候,风铃才是最美观的。蜗蜗每次可以选择一条彩带,将其一端从当前挂钩解下,重新系到另一个挂钩上。操作过程中允许多条彩带的末端临时系在同一个挂钩上,但最终每个挂钩上需要恰好系着一条彩带的一端。

请你帮蜗蜗求出最少操作步数,并给出一种可行的操作序列。

做法

如何思考?

容易想到答案一定是一个旋转若干角度的琵琶。

然后每条弦有 $(x+y)\mod(2n)=rem $.其中注意 \(rem\) 为奇数。

然后当我们旋转后,此时匹配的弦我们肯定不希望动。

考虑一次操作能带来啥影响?

也就是关注一次操作后,各个变量可能发生什么变化。

发现匹配数最多 \(+1\)

那很容易知道一个答案的下界便是 \(n-\max{cnt_{\text{匹配}}}\)

那是不是我们只要每次操作归位一条弦我们就可以构造出下界。

顺序枚举弦,如果匹配就跳过。

若没有呢,我们考虑先随意归位一条,此时如果有条不协调的弦搭着,我们就 \(while\) 做下去。

容易发现是可以一直做下去的,直到当前这个联通块做完。

场上checker没判归位的弦不能重复挂了 66

T2

分两组使按位或的异或值最大。

做法

一开始想偏了想到了一些 trie树上去。

发现并不是很好去考虑一些东西。

首先显然逐位贪心。

想要这一位是 \(1\)

那我们整理一下限制。

就是所有这一位 \(1\) 都要在一个组内。

这个限制看起来就不是很能在 trie 上去做了。

这个显然就是一个并查集。

我们贪心地去填 \(1\),然后每次重构并查集即可。

T3

支持区间按位与和单修,查询区间删去一个元素后的最大按位与。

做法

先考虑去掉一个数最大按位与,咋做?

玩一下发现就是去找第一个当前位只有一个 0 的位,然后把那个 0 对应的数删掉。

看起来就很想拆位去做。

我们拆一下。

按位与变成区间赋值 0。

我们需要查区间 0 的个数。

两 log 无法通过。

再考虑优化,我们考虑合起来一起做这 64 棵线段树。

也就是压位(

我们想维护什么?

首先我们起码得知道哪个是该删的(

也就是说我们得知道区间每一二进制位是否是只有一个 0.

此时最高位就是我们该删的。

那我们能记录区间我们应该删哪个吗?
这个是不行的。

因为有区间 \(and\),容易发现,这个操作做完删哪个就变了。

咋办呢?

那我们得考虑区间 \(and\) 完我们能知道啥?

考虑维护一个集合表示区间哪些二进制位只有一个 0

\(and\) 操作也是简单的。

有了这个我们就可以线段树上二分找出这个该删的数了。

总结:线段树所维护的信息不一定都要维护在树里面,通过线段树二分等操作也能知道很多信息

树上问题

A

给定你一棵根节点为 \(1\) 的树,对于任意的节点 \(u\) 都只能有两种颜色:黑或白。每个节点 \(u\) 的起始颜色都是已知的,记为 \(\mathrm{color}_u\)

现在你有两种操作:

  • 操作 \(1\):把任意一个节点 \(u\) 到根节点的路径上节点的颜色全部翻转(路径包括 \(u\) 和根节点)。
  • 操作 \(2\):把任意一个以 \(u\) 为根节点的子树上的节点颜色全部翻转(\(u\) 的子树包括 \(u\))。

现在问你,最少需要几次操作才能把整棵树变成黑色。

做法

\(f[x][c][t]\) 表示 sub(x) t次(奇偶性)到根路径翻转 ,转移考虑背包。

还有考虑 sub 的翻转所以有3维。\(c\) 表示要不要整体翻转。

值得注意的是,很多信息是可以推出来的,比如是否操作 \(x\) 上下之类的。

应该先合并儿子信息再处理 \(x\) 对操作次数的贡献才是对的。

B

  1. \(x\) 邻居+1
  2. \(a_x:=a_x-v\)
  3. \(x\) 邻居异或和。

做法

不会啊。

前置知识:01trie

01 trie 可以支持插入删除 全局 +1 维护异或和。

从低到高位插入进 trie。

然后考虑 + 1 的过程,事实上就是交换子树 然后继续向 \(1\) 的方向递归。

思路

首先树上问题维护邻域是很困难的。

我们考虑每个点维护儿子信息。再加上父亲信息即可。

操作 \(1\) 就很 01 trie 了。

所以我们考虑每个点对儿子建 01 trie 维护。

查询异或和还得加上父亲,分开维护即可。

一个板子。因为涉及全局 +1这种需要从下往上更新信息的操作。

所以可以写成递归的形式。

const int N = 5e5+5,W = 20;
int n , m ;
vector<int> v[N];
int a[N],fat[N],son[N * W][2],cnt[N * W],val[N*W],tot,root[N],tag[N];
int got(int x){return a[x] + tag[fat[x]];}
// insert erase cnt val 
void pup(int p)
{
	cnt[p] = cnt[son[p][0]] + cnt[son[p][1]];
	val[p] =((val[son[p][1]]^val[son[p][0]])<<1)|(cnt[son[p][1]] & 1);
}
void insert(int &p,int x,int w)
{
	if (!p) p = ++tot;
	if (w >= W) {cnt[p] ++ ;return;}
	insert(son[p][x>>w&1],x,w + 1);
	pup(p);
}
void erase(int p,int x,int w)
{
	if (w >= W) {cnt[p] --;return ; }
	erase(son[p][x>>w&1],x,w + 1);
	pup(p);
}
void addall(int p)
{
	swap(son[p][0],son[p][1]);
	if (son[p][0]) addall(son[p][0]);
	pup(p);
}
// int fat[N];
void dfs(int x,int fa)
{
	fat[x] = fa;
	for (auto y : v[x])
	{
		if (y == fa) continue;
		dfs(y,x);
	}
}

void solvemain()
{
	cin >> n >> m ;
	rep(i,2,n)
	{
		int x,y;cin >> x>>y;
		v[x].pb(y);v[y].pb(x);
	}
	dfs(1,0);
	rep(i,1,n) cin >> a[i];
	rep(x,1,n)
		for (auto y : v[x])
			if (y != fat[x])insert(root[x],a[y],0);		
	while (m -- )
	{
		int op;cin >> op;
		int x,v;cin >> x;
		if (op == 1)
		{
			tag[x] ++ ;
			addall(root[x]);
			const auto fa = fat[x];
			if (fa)
			{
				if (fat[fa]) erase(root[fat[fa]],got(fa),0);
				a[fa] ++ ;
				if ( fat[fa]) insert(root[fat[fa]],got(fa),0);
			}
		}
		else if (op == 2)
		{
			cin >> v;
			if (fat[x]) erase(root[fat[x]],got(x),0);
			a[x] -= v;
			if (fat[x]) insert(root[fat[x]],got(x),0);
		}
		else cout <<( val[root[x]]^got(fat[x])) << endl;
	}

}

C

问树上包含路径任意节点联通块有多少个。

做法

容斥一下 转化为不经过任意节点的方案数,\((?- no x_1)\times (?-no x_2)\cdots\)

容斥系数算一下发现超过 2 的路径的系数都为0.只需要算过点的和过路径的,换根dp即可。

需要注意换根中定义的 \(g_x\) 表示的应该是 \(x\) 为根 \(fa\) 子树的答案。

D - Remove Bridges

\(k\in[1,n-1]\) 条边后桥的最少数量,还要满足桥边的subtree 也全是桥。

此外,根节点只有一个子节点。

做法

初始都是白边,看成每次覆盖一条路径为黑色。要求黑色头上不能有白色。

即对于路径 \((x,y)\)\((lca(x,y),root)\) 的路径应该全是黑色。

一开始想到要一条一条的往上添加路径,然后还有给予一个头顶全黑的限制。

发现顺序根本不重要,因为题目求的是添加 \(k\) 条边后,满足黑色头上不能有白色。

转化为选 \(K\) 条路径覆盖最多。

发现一条路径肯定经过叶子最优。

又因为第一条路径必须经过 \(1\),所以我们最多选 \(2k-1\) 个叶子。

树上链覆盖经典结论:树上不超过2k 个点必能用 k 条链刚好覆盖它们的虚树

证明

考虑 按 dfn 序配对。

  1. 把这 \(2k\) 个点按照从左到右(DFS序)排好队:\(v_1, v_2, \dots, v_{2k}\)
  2. 我们直接把第 \(i\) 个叶子和第 \(i+k\) 个点配对,连一条路径。也就是 \((v_1, v_{k+1}), (v_2, v_{k+2}) \dots\)

我们对于虚树中的每一条边考虑。

如果发现不存在 \(i\) 使得 \(v_i,v_{i+k}\) 分在这条边两端

即左右两端可以自己消化,则 这条边没有被覆盖。

但这是不可能的,点 \(siz = 2k\).

且这条边左右 dfn 连续,而左右的 \(siz_{min} \le k\),故必不可能完全匹配。

贪心 + 长链剖分

接下来问题转化为了选 \(2k-1\) 个叶子,使得到根路径覆盖边最多。

https://www.luogu.com.cn/problem/P10641

考虑朴素贪心,每次选最大权值链,然后清空链权值。

证明就是:

交换法证明贪心。

我们设我们的贪心选取链:\(x_1,x_2,x_3,\dots\)

假设现在有最优解集合:\({S}\)

\(S\) 中没有 \(x_1\),我们令 \(x_1\) 往上走直到走到 \(S\) 覆盖的位置。

这个位置底下必有一个 \(S\) 选的点,我们把 \(x_1\) 与它交换必然不亏。

现在,我们和最优解在第一步达成一致了。把路径权值清空。

树的形态变了(有些边的权值变成了0),但问题依然是:“在一棵新的树上,选 m−1 个叶子,让价值最大。”

归纳。

但是有更好的做法,考虑长链剖分。

我们发现每次都会选择一条剖出来的长链,于是排个序就做完了。

F

Hanh 是一位著名的生物学家。他喜欢在自己的花园里种树并做实验。

有一天,他得到了一棵包含 \(n\) 个顶点的树。顶点编号为 \(1\)\(n\)。一棵有 \(n\) 个顶点的树是一个无向连通图,包含 \(n-1\) 条边。最初,Hanh 将每个顶点的值都设为 \(0\)

现在,Hanh 要进行 \(q\) 次操作,每次操作有以下两种类型之一:

  • 类型 \(1\):Hanh 选择一个顶点 \(v\) 和一个整数 \(d\)。然后他随机均匀地选择一个顶点 \(r\),列出所有从 \(r\)\(u\) 的路径经过 \(v\) 的顶点 \(u\)。Hanh 将所有这样的顶点 \(u\) 的值增加 \(d\)
  • 类型 \(2\):Hanh 选择一个顶点 \(v\),并计算 \(v\) 的期望值。

由于 Hanh 擅长生物学但不擅长数学,他需要你来帮助完成这些操作。

做法

极劣根号做法。

拆贡献,然后发现有一个 \(v_{son}:= v_{son} - siz_{son}\times d/n\) 的事情。

其他是好做的。

这个 直接根号分治,小的直接做,大的记下来。

然后在统计 \(x\) 的答案的时候枚举大的点,再二分出大的是哪个子树包含了 \(x\),算上贡献即可。

poly

轻重链剖分

重儿子直接做 轻儿子打标记 然后 查询向上跳轻边。

因为轻重链切换是log的

E

给定一棵树,\(1\) 根,初始 \(1\) 激活,然后每次会激活一个当前的叶子,问是否存在完美匹配,输出匹配边之和。

做法

树上匹配如何考虑?

有结论当 \(\# (siz_x \mod 2 = 0) = n/2 \text{且} n\mod 2=0\) 时有完美匹配。

怎么证明呢?

我们尝试把整棵树切成 \(\frac{n}{2}\) 个块,每块大小为 \(2\)

于是我们从下到上贪心地切,当大小为偶数就切,若切了 \(\frac{n}{2}\) 刀那就是有完美匹配。

转化为维护 \(siz\),就是树上链 \(+1\),维护奇数即可,树链剖分。

树上问题 2

A

权值 012,问能最多割下多少个联通块满足,每块 sum = k。

\(n\le 10^6\)

做法

首先贪心是必须想到的。

从下到上,找到 \(x\) 满足 \(subtree(x)\) 可以切出 \(k\),且 \(subtree(x) \setminus x\) 都不行。

此时我们切出来是不劣的。

证明:

假设最优解中没有我们的操作,则 \(x\) 必在一个切出的联通块中(反之,加上 \(sub(x)\) 的块答案变大).

则我们把这个换成我们的块,答案不变。

问题转化为判定 \(subtree(x)\) 能否切出 \(k\).

思考路径

一种思考路径是假设存在。

假设存在大小为 \(k\) 的块。

会发生什么?

先去掉 \(0\) 叶子。

剩下 \(\ge 2\) 个叶子。

若有 \(v = 2\) 叶子,去掉得到 \(k-2\).

若全是 \(v=1\) 的叶子,都去掉得到 \(k-2\).

也就是说我们发现能否拼出只关注奇偶分别的最值。

于是树形 dp 就做完了。

总结

与其仔细研究何时可行或何时不可行,不如假设可行研究性质。

C

一棵树问两个路径上出现次数不同的颜色。

容易想到主席树,然后我们把两棵线段树减一下,然后二分0.

B

输出一棵树前 \(k\) 大的路径长度。

首先考虑二分第 \(k\) 大的长度。

转化为求 \(\le len\) 的数量。

这个可以直接点分治 + 双指针。

由于我们要二分,所以我们可以预处理点分治。

我们保存:

all:
所有点到 c 的距离
包括 c 自己,所以有一个 0

sub[i]:
第 i 个子树所有点到 c 的距离

并全部排序。

二分求出第 \(k\) 大长度为 \(T\)

如何输出?

我们可以先输出所有 \(>T\) 的路径,然后再补 \(T\) 直到有 \(k\) 个数。

直接用个 multiset 维护一下前面的子树即可。

模拟赛

350。

T1

二分+check

T2

蜗蜗是一位观星高手。天文台有 mm 个观星台围成一个圆圈,按顺时针方向依次编号为 11 到 mm,每个观星台可以容纳任意数量的游客。共有 nn 位游客,第 ii 位游客最初被安排在观星台 AiA**i

如果第 ii 位游客最终位于观星台 jj,他将获得 Hi,jH**i,j 的满意度(满意度可能为负数)。蜗蜗可以进行任意次(包括零次)位置调整操作,每次操作如下:

  • 选择两位不同的游客 ii 和 jj
  • 让游客 ii 顺时针移动 XX 个观星台,同时让游客 jj 逆时针移动 XX 个观星台。

蜗蜗希望通过若干次操作,使得所有游客的总满意度达到最大。请你帮蜗蜗计算这个最大值。

考虑充要条件:$sum \mod m $ 不变 且 \(p_i \mod g\) 不变。

第一个是显然的,第二个就是因为他只能在 \(g\) 的剩余系里面动。

然后我们逐个dp每次枚举他动几下即可。

T3

给定一个只有一种完美匹配的二分图,然后你可以把信给一个点

任何点收到后,若匹配没有,则传给匹配,反之则可以任意传给一个没有信的人。

问信最长路。

做法

先做第一部分,怎么找匹配?

注意到有结论若一个有完美匹配的联通块没有奇点,则必有两种匹配。

因为你先连上匹配边,然后由于没有奇点,每个点都会向外连,则必有几条匹配边被连成环。

后面就比较简单。

因为你走到黑点就会传送到白点,并且路径一定是黑白黑白 \(\dots\)

不妨设从黑出发,我们可以把匹配边看成一个点,然后跑 dag 最长路。

为啥是 dag?

因为若有环,则必然同第一部分一样,有多种匹配。

T4

一个 深度 \(<35\) 的树,问选一个首都为根,再选几个城市,满足任意城市不成祖先关系的方案数.

做法

\(n^3\) 是简单的。

然后我们会有一个换根的做法。

考虑换根 + 退背包。这里的背包其实就是一个多项式卷积。

\(f_x\) 表示 \(subtree(x)\) \(x\) 不选的方案数。 好算 \(O(n^2)\)

\(g_x\) 表示去掉 \(subtree(x)\)\(fa_x\) 不选的方案数。

对于转移 \(g_x \rightarrow g_y\),我们需要先合并 \(f_x\)\(g_x\),再考虑退掉 \(f_y\) 的贡献。

这个直接考虑大除法。

具体来说,考虑这样递推

\[c_i = \sum_j a_j\times b_{i-j} \]

\[c_i = \sum_{j=0}^{i-1} a_j\times b_{i-j}+ a_i\times b_0 \]

\[ a_i =(c_i- \sum_{j=0}^{i-1} a_j\times b_{i-j})\times \frac{1}{b_0} \]

值得注意的是多项式 \(A \times B = C\) 的时间复杂度是 \(O(|A||B|)\)

同理 \(\large \frac{C}{B} = A\) 的复杂度也是 \(O(|A||B|)\).

分析一下复杂度。

每个 \(x\) 合并,\(\sum siz_x\times (n-siz_x)\).

退掉 \(f_y\)\(\sum siz_y\times (n-siz_y)\)

这个看起来跟题目给的深度没啥关系。

但是我们有:

\(\sum siz_i = \sum dep_i\) 这个因为可以上到下和下到上算。

然后这个就优化了。

时间复杂度 \(O(n^2 \times 35)\)

code

代码是简单的

vector<int > merge(vct<int> &a,vct<int> &b)
{
	vct<int> c(sz(a) + sz(b)-1);
	for (int i = 0 ;i < sz(a);i ++ )
		for (int j = 0 ;j < sz(b);j ++ )
			add(c[i + j ], a[i] * 1ll *b[j]%mod);
	return c;
}

vector<int> chu(vct<int> &c,vct<int> &b)
{
	vct<int> a(sz(c) - 1 - (sz(b) - 1) + 1);	
	int inv = ksm(b[0],mod - 2);
	for (int i = 0 ;i < sz(a);i ++ )
	{
		a[i] = c[i];
		for (int j = max(0,i-sz(b)+1); j < i;j ++ )
			sub(a[i],a[j] * 1ll * b[i - j ] % mod);
		a[i] = a[i] *1ll * inv % mod;
	}
	return a;
} 

void dfs0(int x,int fa)
{
	siz[x] = 1;
	f[x].assign(siz[x]+1,0);f[x][0] = 1;
	for (auto y : v[x])
	{
		if (y == fa) continue;
		dfs0(y,x);
		f[y][1] ++ ;f[x] = merge(f[x],f[y]);f[y][1] -- ;
		siz[x] += siz[y];
	}
	 
}
void dfs1(int x,int fa)
{
	vct<int> h;
	if (x == 1) h = f[x];
	else {
		while (sz(g[x]) < 2) g[x].pb(0);
		g[x][1] ++;h = merge(f[x],g[x]);g[x][1] -- ;
	}
	for (auto y : v[x])
	{
		if (y == fa) continue;
		f[y][1] ++;g[y] = chu(h,f[y]);f[y][1] -- ;
		dfs1(y,x);
	}
}

void solvemain()
{
	cin >>n ;
	rep(i,1,n - 1)
	{
		int x,y;cin >> x >>y;v[x].pb(y);v[y].pb(x);
	}
	dfs0(1,0);	
	dfs1(1,0);	
	vector<int> ans(n + 2);
	vct<int> h;
	rep(x,1,n)
	{
		if (x == 1) h = f[x];
		else g[x][1] ++,h = merge(f[x],g[x]);
		rep(i,0,min(n - 1,sz(h) -1))
			add(ans[i + 1],h[i]);
	}
	rep(i,1,n)	cout<< ans[i] << ' ' ; cout << endl ;
	
}

大构大构交交交

A

二叉树每次询问关键点有没有在 \(x\)\(\le k\) 的路径内。

40次问出关键点。

二叉树是关键的。

我们先考虑一个满二叉树的case,要求关键点在叶子。

很容易想到考虑类似二分的过程。

那我们想能否在上面找到一棵满二叉树。

我们可以先确定关键点深度,然后该深度的点都可能为关键点,我们再考虑二分即可。

但是这样是错的。

不妨设 n=m(m+1)/2,构造 n 条长度分别为 1,2,3...,n 的链,然后把链首连起来。

这样就卡到了 \(O(\sqrt n)\).

正解

还是先问层数。

现在我们确定了一些待定点。

我们考虑每次排除一些。

怎么样能排除最多呢?

考虑树的重心。

由于二叉树,\(deg\le 3\)

故最大联通块 \(\frac{n}{3} \le siz \le \frac{n}{2}\) .

维护剩余点集的重心,每次问重心的重儿子由于是二叉树,这个 size 一定 > 1 /3 n,所以每次一定能删去 1/ 3 的点,总次 数 log1.5 (n)。

B

https://qoj.ac/problem/16000/statement/zh_cn

35000次操作,\(n \le 500\),一个队列,可以 \(pop,push\) 每次返回队列中颜色数。

最后要按颜色分组输出。

思路

首先我们只需要分组,意味着这可能是一个类似并查集的事情。

确定性的分治做法。

容易先找出所有颜色第一次出现位置,我们称为代表元。

然后对于那些不确定的位置,我们考虑分治。

分为 \(h1,h2\).

把一个 \(h\) 全部丢进队列。

再一个个把代表元丢进去,若增加则代表有。

这样我们就可以分成两份继续递归。

关于如何想到

可以这样想,我们现在已知一些代表元。

我们想知道对应关系。

注意到可以一次批量操作求出出现在一个集合内的所有颜色代表。

虽然不能知道具体对应关系,但在不断缩小候选集合的分治过程中可以做到。

C

通信,要求给树边定向,还原出一个 \(n-1\) 位01 串。

定向可以看成是有一个 \(0/1\) 的边权。

边跟串是一一对应的。

再考虑树有啥性质,发现树的形态是多变的很难在形状上传递一些信息。

但树有最基本的一条性质,联通。

故我们考虑 \((x,y)\) 边权值是 \(s_x \oplus s_y\) 这样在树上通过一个点就可以传递所有信息了。

D

序列,选 3 数换成他们的异或,构造 n 次操作使得全0 。

做法

依旧神秘操作。

考虑找不变量。

试下前缀异或。

发现是一个 \(1,2,3,4 \rightarrow 1,4,1,4\) 的操作。

发现奇偶互不影响。

首先偶数是容易的因为 \(s_0 = 0\)

那么若奇数无 \(0\),无解,反之我们分别扩展奇数和偶数的0即可。

E

石头比礼物重,每次可以把两个子集放上天平,问编号最小的礼物。石头超过一半。非自适应

假设第一个是石头,我们可以不断倍增。

变成判定第一个是否是石头。

由于非自适应,考虑随机化。

可以每次随机问一个,若不是石头,则有 \(>\frac{1}{2}\) 的概率测出来,多做几次即可。

F

\(2^K\) 的序列建立线段树,线段树区间节点中只有一个黑点,每次可以线段树上区间询问,会返回线段树上的节点是否有黑点 。 自适应

做法

自适应肯定要覆盖所有节点。

我们先考虑一个覆盖所有节点的下界。

性质 1:每一次线段树询问最多问到两个同一层的点;

性质 2:当询问有点在最左侧/最右侧时,该次询问最多只能问到一个同一层的点。

考虑最下层,我们有下界 \((2^k - 2k) / 2 +2k + 1 = 2^{k-1} + k + 1\) 次询问。

考虑这样构造,\(k = 1\) 直接全问 ,\(k>1\) ,我们考虑合并两个块,然后中间边界起手的区间可以合并。

而最上层的交换合并即可。

现在既然知道这是最优的覆盖方案,我们在查出来的区间组里面二分即可。

int query(int, int);

pair<int,int> solve(int k ){
    const int n = (1<<k);
    vct<array<int,3> > rng((1<<(k + 1)));
    auto merge =[ &](int x,int y)
    {
        rng[x]= {min(rng[x][0],rng[y][0]),
        max(rng[x][1],rng[y][1]),rng[x][2] + rng[y][2]};
        // rng[x][2] += rng[y][2];
        rng[y] = {-1,-1,0};
    };
    auto dfs = [&](int k,int l,int r,auto &dfs)
    {
        int mid = l + r>> 1;
        if (r-l+1 <= 2)
        {
            rng[k] = {l,r,1};
            rng[k << 1] = {l,mid,1};
            rng[k<<1|1] = {mid + 1,r,1};
            return ; 
        }
        dfs(k<<1,l,mid,dfs);
        dfs(k<<1|1,mid + 1,r,dfs);
        rng[k] = {l , r,1};
        int x= k << 1,y = k << 1 | 1;
        merge(x,y << 1);
        merge(y,x << 1 | 1);
        x = x << 1 | 1;y = y << 1; 
        while (x < n && y < n ) // fei leaves
        {
            x = x << 1 | 1;
            y = y << 1;
            merge(x,y);
        }
    };
    dfs(1,1,n,dfs);
    
    sort(all(rng),[&](array<int,3> a,array<int,3> b)
    {
        return a[2] > b[2];
    });
    while (rng.back()[0] <= 0) rng.pop_back();
    for (int o = 0 ; o < sz(rng);o ++ )
    {
        auto [ l , r,cnt] = rng[o];
        if (l <= 0 || r <= 0) continue;
        int res;
        if (o < sz(rng) - 1) res = query(l, r);
        else res = 1;
        if (res == -1) return {-1,-1};
        if (res)
        {
            vct<array<int,2> > v;
            auto got = [&](int k,int l,int r,int x,int y,auto &got)
            {
                if (x > r  || y < l ) return  ;
                if (x <= l && r <= y)
                    return v.pb({l,r}),void();int mid = l + r >> 1;
                got(k<<1,l,mid,x,y,got);
                got(k<<1|1,mid+1,r,x,y,got);
            };
            got(1,1,n,l , r,got);
            

            int l = 0,r = sz(v) - 1;
            while ( l < r)
            {
                int mid = l + r>> 1;
                
                int res = query(v[0][0],v[mid][1]);
                if (res == -1) return {-1,-1};
                
                

                if (res) r = mid;
                else l = mid + 1;
            }
            return {v[l][0],v[l][1]};
        }
    }
    return {-1,-1};
}

超李树

待补

Beautiful tree

倍增优化建图。https://www.luogu.com.cn/problem/CF1904F

  • “1 a b c” —— 在节点 ab 之间的路径上,值最小的节点必须是 c

  • “2 a b c” —— 在节点 ab 之间的路径上,值最大的节点必须是 c

做法

我们考虑为这个条件搞一个拓扑的限制。

\(\forall i,c \ge x_i\)\((a,b)\) 路径上。

那我们可以考虑拓扑序约束。

在拓扑序上, \(x\rightarrow y\),则有 \(c_x < c_y\)

则我们考虑这样建边。

变成路径上到一个点右边,倍增优化建图即可。

\(f_{x,k}\) 表示一个虚点,这个点会连向\(x\)\([0,2^k-1]\) 级祖先。

转移有 \(f_{x,k} \rightarrow f_{x,k-1}\)

\(f_{x,k} \rightarrow f_{x+2^{k-1},k-1}\)

反过来同理。

这样,我们边数就控制在了 一个 \(log\),我们就做完了。

结营小记

A

给出一个数组及其前缀和的集合。

然后构造一个合法数组。

做法

刻画一下数组及其前缀和。

类似于:

\[x_1 ... ... ..x2 .........x3...... \]

\[x_1 x1 ...x1 x2 .......x2 x3...... \]

若干 \(L\) 型。

\(c_x = 1\) 则不能构成 \(L\)

小的可以放在大的 \(L\) 上。

贪心是容易想到的。

因为你考虑两个 \(L\) 在一起实际上没有啥影响。

调整一下是一定能放的。

真正影响有解的是 \(c_x = 1\)

所以我们可以这样考虑。

从小到大地依次考虑。

记录 \(cnt\) 为前面的\(\#c_x = 1\)

需要我们就造 \(L\),不需要我们也可以自己叠两个上去。

D

给点 \(a_i\),完全图求最大瓶颈路。

一个朴素的想法就是优化建图建出最小生成树,\(kruskal\) 重构树,然后求 lca。

然后我们再思考一下。

这个是不是完全不需要。

我们可以离线询问,然后按照从大到小启发式合并。

E

题意就是队列增加 \(x\)\(col\),然后如果长度超过 \(m\) ,就弹。

每次问队列第 \(k\) 个颜色,并删除。

做法

一开始的想法是不进行弹队头操作,查询 \(kth\) 转化成查询 \((m-k) th\)

但是这是错的。

因为有删除操作,删除操作会导致一些错误。

然后我们容易有一个想法。

我们维护一块块的 \(col\),然后查询的话可以二分在哪一个块。

删除就是简单地块 \(cnt \leftarrow cnt-1\).

在及时维护队头,若超过 \(m\),我们就减队头。

然后我们就可以用树状数组上二分简单维护了。

F

P8580 [CoE R5] 罚球

\(n\) 个人在玩罚球游戏,游戏规则如下:

  • 每个人编号为 \(1,2,\dots,n\),最开始由 \(1\) 号罚球,接下来让下一个没有出局的人罚球。特殊地,\(n\) 号的下一个是 \(1\) 号。
  • 如果罚球者没有碰到篮板,那么直接出局。
  • 如果罚球者碰到篮板但没有进球,那么如果上一个人进球了,这个人就会出局,否则不会出局。
  • 游戏结束的条件是最后只剩下一个人。

注意最开始的那个人碰到篮板但没有进球不出局。

\(n\) 个人中,第 \(i\) 个人碰不到篮板的概率为 \(\dfrac{a_i}{1000}\),碰到篮板但没有进球的概率为 \(\dfrac{b_i}{1000}\),求游戏结束时所有人总共罚球数量的期望值。

做法

考虑直接写出 状压 dp 式子。

然后我们按 \(st\) 分层,每一层里面有两个环,然后我们把这个环里面用主元法做,其他直接转移即可。

void solvemain()
{
	cin >> n >> t;
	inv1000 = ksm(1000,mod - 2);
	int cnt1 = 0 ,cnt2 = 0 ;
	for (int i = 0;i < n;i ++ ){
		cin >> a[i] >>b[i];
		c[i] = 1000 - a[i] - b[i];
		a[i] =a[i]*1ll *inv1000%mod;
		b[i] =b[i]*1ll *inv1000%mod;
		c[i] =c[i]*1ll *inv1000%mod;
		cnt1 += c[i]==1;
		cnt2 += b[i] ==1;
	}
	if (cnt1 >= 2 || cnt2 >= 2&&cnt1==0 )
	{
		cout << -1 << endl;return;
	}
	auto add = [&](int &x,int y)
	{
		x += y;if (x >= mod) x-= mod;
	};
	// return ; 
	for (int st = 1;st < (1<<n);st++){
		if ((1<<__lg(st)) == st) continue; 
		
		//1
		vct<int> p;
		rep(i,0,n - 1) if (st >> i & 1) p.pb(i);
		
		array<int,2> now = {1,0};
		for (int o = sz(p) - 1;o >= 0;o--)
		{
			int nxo = (o + 1)%sz(p);
			for (auto &t : now ) t=t*1ll*c[p[o]] % mod;
			add(now[1],
			(a[p[o]] + b[p[o]])%mod*1ll * f[st^(1<<p[o])][0][p[nxo]]%mod
			);
			add(now[1],1);
		}
		// cout << now[0] << ' ' << now[1] << endl;
		int X = ksm((1-now[0]+mod)%mod,mod- 2) * 1ll * 
		now[1] %mod;
		now = {1,0};
		for (int o = sz(p) - 1;o >= 0;o--)
		{
			int nxo = (o + 1)%sz(p);
			for (auto &t : now ) t=t*1ll*c[p[o]] % mod;
			add(now[1],
			(a[p[o]] + b[p[o]])%mod*1ll * f[st^(1<<p[o])][0][p[nxo]]%mod
			);
			add(now[1],1);

			f[st][1][p[o]] = (now[0]*1ll*X%mod+now[1])%mod;
		}

		// 0
		
		now = {1,0};
		for (int o = sz(p) - 1;o >= 0;o--)
		{
			int nxo = (o + 1)%sz(p);
			int D = a[p[o]]*1ll*f[st^(1<<p[o])][0][p[nxo] ]%mod;
			add(D,c[p[o]]*1ll*f[st][1][p[nxo]]%mod);
			add(D,1);
		
			
			for (auto &t : now ) t=t*1ll*b[p[o]] % mod;
			add(now[1],D);
		}
		X = ksm((1-now[0]+mod)%mod,mod- 2) * 1ll * 
		now[1] %mod;
		now = {1,0};
		for (int o = sz(p) - 1;o >= 0;o--)
		{
			int nxo = (o + 1)%sz(p);
			int D = a[p[o]]*1ll*f[st^(1<<p[o])][0][p[nxo] ]%mod;
			add(D,c[p[o]]*1ll*f[st][1][p[nxo]]%mod);
			add(D,1);
		
			
			for (auto &t : now ) t=t*1ll*b[p[o]] % mod;
			add(now[1],D);
			f[st][0][p[o]] = (now[0]*1ll*X%mod + now[1]) %mod;
		}
	
		
		// rep(i,0,) cout << 
	}
	
	cout << f[(1<<n) - 1][0][0] << endl ; 
}

posted @ 2026-08-28 08:36  codwarm  阅读(5)  评论(0)    收藏  举报