0%

题目大意

  1. 任务目标

已知一个隐藏的长度为 nn 的排列 pp你需要找到一个字典序最小的排列 qq,使得 qq 满足以下两个条件:

  • q>pq > pqq 的字典序大于 pp

  • rev(q)>rev(p)rev(q) > rev(p)qq 翻转后的字典序大于 pp 翻转后的字典序)

注意: qq 必须是由 pp 中的元素重新排列得到的。你不需要直接输出 qq,而是输出一个下标排列 rr (1rin1 \le r_i \le n),使得 qi=priq_i = p_{r_i}。如果不存在满足条件的 qq,输出 1-1

2. 交互规则

  • 你可以进行询问,格式为 ? i j,查询 pi<pjp_i < p_j 是否成立。

  • 如果 pi<pjp_i < p_j,交互库返回 11;否则返回 00

  • 每个测试点最多允许询问 3n3n 次。

  • 交互库是非自适应的(隐藏排列 pp 在交互开始前已确定)。

3. 样例解释

  • 样例 1: p=[5,4,2,3,1]p = [5, 4, 2, 3, 1]

    • 满足条件的最小 qq[5,4,3,1,2][5, 4, 3, 1, 2]
    • 验证:qqpp 字典序大;rev(q)=[2,1,3,4,5]rev(q) = [2, 1, 3, 4, 5]rev(p)=[1,3,2,4,5]rev(p) = [1, 3, 2, 4, 5],显然 rev(q)>rev(p)rev(q) > rev(p)
    • 输出 r=[1,2,4,5,3]r = [1, 2, 4, 5, 3],因为 p1=5,p2=4,p4=3,p5=1,p3=2p_1=5, p_2=4, p_4=3, p_5=1, p_3=2
  • 样例 2: p=[3,1,2,4]p = [3, 1, 2, 4]

    • 不存在同时满足 q>pq > prev(q)>rev(p)rev(q) > rev(p) 的排列 qq,故输出 1-1

4. 数据范围

  • t1000t \le 1000n2104n \le 2 \cdot 10^4,所有测试用例的 nn 之和不超过 21042 \cdot 10^4

  • 时间限制 2 秒,内存限制 256 MB。

思路讲解

一开始看这道题目,不能看出来像下面这张图的解法,但是我们会误以为这个情况的一些其他性质比较重要,但是最重要的是单调性的变化,因为没有单调性的变化意味着没有这个答案。

因此,这道题目关键点在于这个单调性的变化(即未发生单调性变化下操作是没有意义的,一定会违反题设条件),我们先来看这个情况,不难发现,在单调性变化之后执行交换与这个反转操作最好。

image

那么如果先下降呢?经过对拍验证以及这个推理,如果先下降再上升,是无法构造出答案的。原因:设在当前点,你需要构造 qq,以满足题设条件。因此,你要提高 qq 的字典序,那么必须要在后面的点当中选一个比当前点高的(最优的是恰好高的),但是这样一来,就会导致 rev(q)\text{rev}(q) 的字典序下降(这个是无法挽救的,因为目前的更换的当前点处于上升序列,处于上升序列的其他点比当前点低,处于下降序列的其他点比当前点低),导致无法满足题设条件。

image

因此先下降,再上升的话,必须要再找一个下降。

image

然后,交换好以后,就想办法把这个上升序列插入到这个下降序列当中(有序插入,保持单调下降性),随便你用什么办法。

然后为了减少提问个数,我们使用了记忆化,就是问过的,就不再问了(注意问了 a b 的话,b a 也不要再问了)。

感觉这道题目难度主要还是在这个调试,代码和细节上。

AC代码

AC
https://codeforces.com/contest/2191/submission/359093232

心路历程(WA,TLE,MLE……)

题目大意

1. 核心定义

  • 括号序列比较 (“Better Than”):
    对于两个括号序列 aabb,满足以下条件之一则称 aabb “更好”:

    1. bbaa 的前缀,且 aba \neq b
    2. 在第一个不相同的字符位置 iiai=(a_i = '('bi=)b_i = ')'
  • 序列 tt 的得分 (Score)

    1. 如果 tt 不是正则括号序列 (RBS),得分为 00
    2. 如果 tt 是 RBS,查找其所有的正则括号子序列 rr。如果存在某些 rr 满足 rrtt “更好”,则得分为这些 rr 中长度 r|r| 的最大值。
    3. 若不存在这样的 rr,得分为 00

2. 任务要求

给定一个长度为 nn (1n1001 \le n \le 100) 的括号序列 ss,求 ss 的所有非空子序列的得分之和,结果对 998244353998244353 取模。


3. 样例解释

  • 样例 2:s = ()()()
    考虑其中一个子序列 t="()()()"t = "()()()"

    • 它是一个 RBS。
    • 它有一个子序列 r="(())"r = "(())"(取原序列第 1, 3, 4, 6 位)。
    • 比较 ttrr:第一位都是 (,第二位 t2=)t_2 = ')'r2=(r_2 = '('。根据定义,rrtt 更好。
    • rr 的长度为 44,经校验这是最长的更好子序列,故该子序列 tt 得分为 44
    • 其他子序列得分均为 00,总和为 44
  • 样例 3:****s = (())()

    • 对于子序列 t="(())()"t = "(())()",虽然它是 RBS,但无法找到比它“更好”的 RBS 子序列。
    • 例如,要在某位置 ii 更好,必须 ti=)t_i = ')'ri=(r_i = '(',但在该位置之前,tt 已经用完了所有的 ( 匹配了前面的 ),无法构造出更优的正则子序列。
    • 因此总和为 00

思路讲解

首先,D1 当中,我们发现一个规律,就是只要 ss 串只要是有答案的,其答案就是 s2|s|-2,那么其有答案的条件是第一个 )) 出现的位置后,又至少出现了出现了两个 ((

我们可以定义一个如下状态:

image

⚠️注意:这里的长度,个数,指的是合法方案个数及其合法方案的总(后缀)长度,这样子定义是为了方便记忆化搜索。

总体上的记忆化搜索过程可以描述为这样:

res(0,0)if unbalance=0state=3:res(1,1)lresdfs(idx,unbalance,state)if idx0:lreslenlreslen+lresnumresres+lres\begin{aligned} & res \gets (0,0) \\ & \text{if } unbalance = 0 \land state = 3: res \gets (1,1) \\ & lres \gets \text{dfs}(idx, unbalance, state) \\ & \text{if } idx \neq 0: lres_{\text{len}} \gets lres_{\text{len}} + lres_{\text{num}} \\ & res \gets res + lres\end{aligned}

那么之所以不在 idx=0idx=0 的时候进行这个操作,是因为 0 是虚拟节点,没有实际字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
auto dfs=[&](auto && self,int idx,int unbalance,int state) -> Len_num{
if (unbalance<0) {
return {0,0};
}
if (vis[idx][unbalance][state]) {
return dp[idx][unbalance][state];
}
vis[idx][unbalance][state]=true;
// assert(idx<=N);
Len_num res={0,0};
if (unbalance==0 && state==3) {
// 相当于只选这个 s【idx】的时候
res={1,1};
}
for (int i=idx+1;i<=N;++i) {
Len_num lres;
if (s[i]==')') {
lres=self(self,i,unbalance-1,max(1,state));
}else {
int to_state;
if (state==0) to_state=0;
else to_state=min(3,state+1);
lres=self(self,i,unbalance+1,to_state);
}
res+=lres;
// 0 是一个虚拟节点,不会增加这个 res 长度
if (lres.len!=0 && idx!=0) {
// 这里给所有的方案增加一个长度,即选了
res.len+=lres.num;
}
res%=mod;
}
dp[idx][unbalance][state]=res;
return res;
};
Len_num res=dfs(dfs,0,0,0);

AC代码

AC
https://codeforces.com/contest/2191/submission/358546432

心路历程(WA,TLE,MLE……)

res{0,0}unbalance=0 and state=3:res{1,1}lresdfs(idx,unbalance,state)idx0:lres.lenlres.len+lres.numresres+lresres\gets \{0,0\}\\ unbalance=0 \text{ and } state=3:res\gets \{1,1\} \\ lres\gets \text{dfs}(idx,unbalance,state)\\ idx≠0:lres.len\gets lres.len+lres.num\\ res\gets res+lres

题目大意

Alice 和 Bob 在长度为 n 的二进制字符串 s(仅包含字符 0 和 1)上进行博弈游戏。Alice 先手,双方轮流操作。

游戏规则:

  • 每回合,玩家选择一个索引序列 i₁, i₂, …, iₘ(严格递增),使得这些位置的字符形成非增序列(s[i₁] ≥ s[i₂] ≥ … ≥ s[iₘ])

  • 然后将这些位置的字符重新排列为非减序列(先放所有 0,再放所有 1)

  • 有效移动必须严格改变字符串(即选中的字符中至少有一个 0 和一个 1)

  • 无法进行有效移动的玩家输掉游戏

任务:

  • 假设双方都采用最优策略,判断谁会获胜

  • 如果 Alice 获胜,输出她的一个获胜首步移动(索引序列)

输入格式:

  • 多组测试数据,第一行包含测试数据组数 t(1 ≤ t ≤ 10⁴)

  • 每组数据:第一行为字符串长度 n(1 ≤ n ≤ 2×10⁵),第二行为二进制字符串 s

  • 保证所有测试用例的 n 之和不超过 2×10⁵

输出格式:

  • 如果 Bob 获胜,输出 “Bob”

  • 如果 Alice 获胜,输出三行:第一行 “Alice”,第二行整数 m(选中的索引数量),第三行 m 个递增的索引

样例说明:

  • 样例 1(000):字符串已经有序,无法进行任何有效移动,Bob 直接获胜

  • 样例 3(10):Alice 可以选择位置 [1,2],将字符串变为 01,之后 Bob 无法移动,Alice 获胜

思路讲解

其实我们会发现,只需要一次操作即可解决这个问题

AC代码

AC

https://codeforces.com/contest/2191/submission/358351618

心路历程(WA,TLE,MLE……)

题目大意

问题描述:

  • 给定一棵有 n 个顶点的有根树,根节点为 1,所有顶点初始为白色

  • 定义 $d_i $ 为根节点第 i 个顶点的距离

操作规则:

  • 每次操作可以选择一个白色顶点的子集 S

  • S 中的任意两个节点必须满足:

    • 不通过边相连(不相邻)
    • 到根节点的距离不相同(不在同一层)
  • 将 S 中的所有顶点涂成黑色

目标:

  • 求将整棵树全部涂成黑色所需的最少操作次数

数据范围:

  • 测试用例数 t: 1 ≤ t ≤ 10⁴

  • 顶点数 n: 2 ≤ n ≤ 2×10⁵

  • 所有测试用例的 n 之和不超过 2×10⁵

版本说明:

  • 这是简化版本,只需要输出最少操作次数

  • 困难版本(D2)需要输出具体的操作方案

启示:当我们发现一道题目需要猜结论,但是我们没啥思路的时候,不妨先猜下界,然后想办法不断提升这个下界(特别是对于这种个数推断与猜测)。

思路讲解

easy version,我们想要搞一个这个 dp,但是就出现这个问题了,因为和别的子树的交互,删除,没有考虑到,这个是树上 dp 的弱点,要么想办法在这个转移/合并的时候解决这个问题,要么就换做法。

发现这个 easy version 过题比较多,那么就要想到,可能就是猜结论了,不过直接猜出来比较难,我们可以先猜猜下界。首先,设 tit_i 为第 ii 层的节点个数,那么 ansmax(t1,,tn)ans≥\text{max}(t_1,\cdots,t_n) 是肯定的,不过,如果说 ii 层的这个所有节点都共用一个根节点,这个时候还需要对 ti+1t_i+1 取 max 值。(这个就是 easy 版本的结论)

那么 hard 版本就相当于构造性证明这个结论。

不过,想办法构造比较困难,我们不如交给随机化算法。即,我们的这个构造实际上可以看作为给这个节点涂颜色,要求节点颜色种类不能超过 ansans,并且和父节点颜色不同。我们仅仅只是使用一个这个 holeColorholeColor 的贪心进行一个瞎搞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for (int i=1;i<=mx_dep;++i) {
while (true) {
shuffle(all(dep_node_mtx[i]),rng);
ll holeColor=-1;
ll cur_color=1;
for (auto u:dep_node_mtx[i]) {
if (holeColor!=-1 && holeColor!=color[parent[u]]) {
color[u]=holeColor;
holeColor=-1;
continue;
}
if (cur_color!=color[parent[u]]) {
color[u]=cur_color;
cur_color++;
}else {
holeColor=cur_color;
cur_color++;
color[u]=cur_color;
cur_color++;
}
}
if (cur_color<=ans+1) {
break;
}
}
}

AC代码

AC
https://codeforces.com/contest/2183/submission/358257705

心路历程(WA,TLE,MLE……)

走了一些弯路,做这种图论的题目,还是需要更有整体意识

顺序会影响结果的原因很朴素:这段构造是“局部贪心”,它不会为后面的点“预留”某个关键编号

同一层里每个点只禁用 1 个编号(父亲的 du[parent]),但“哪个点拿走哪个编号”是有讲究的;你现在这套贪心只维护 一个洞 g,一旦把洞用在了“本来很灵活的点”身上,就可能把后面那个“更挑剔的点”逼到只能用更大的编号,最后 > ans 失败。

我用一个最小反例把它讲清楚(这就是“为什么顺序影响结果”的精髓):


反例:明明有解,但某个顺序会让贪心爆掉

设这一层有 3 个点 (A,B,C),D1 算出来最优 (K=ans=3)。

它们父亲的编号分别是:

  • (du[parent(A)] = 1) ⇒ (A) 不能用 1

  • (du[parent(B)] = 2) ⇒ (B) 不能用 2

  • (du[parent©] = 3) ⇒ © 不能用 3

显然存在合法分配(且都在 1…3 内):

du[A]=2, du[B]=3, du[C]=1du[A]=2,\ du[B]=3,\ du[C]=1

都互不相同,也都避开了父亲的编号。


但你的贪心在顺序 A→B→C 时会失败

你的规则(对应代码)是:

  • t:按 1,2,3… 递增发新编号

  • 如果当前点禁用 t,就“跳过”它:把 t 存到洞 g,然后给它 t+1

  • g 存在且当前点不禁用 g,就先把洞 g 填给它

现在按顺序 A, B, C 跑:

处理 A(禁 1)

  • t=1,但 A 禁 1 ⇒ 产生洞 g=1t 再加到 2

  • 给 A:du[A]=2

处理 B(禁 2)

  • 此时 g=1,而 B 的父亲编号是 2,不等于 1 ⇒ 贪心立刻把洞填掉

  • 给 B:du[B]=1,洞清空

处理 C(禁 3)

  • 现在已用掉 {2,1},t 会走到 3

  • 但 C 禁 3 ⇒ 贪心只能跳过 3,给它 4

  • 得到 du[C]=4 > ans(=3)这层构造失败,重来

所以你看到的现象就是:同样的一层,同样的 K,有的顺序一把过,有的顺序必炸。

题目大意

背景

有 n 个基地排成一条线,第 k 个基地是主基地(home base)。初始时主基地有 1 个士兵。

规则

每天按顺序发生以下事件:

  1. 选择一个基地 i 和该基地中任意数量的士兵(可以是 0 或全部),命令这些士兵向左移动到基地 i-1 或向右移动到基地 i+1(所有士兵必须同方向移动,且不能移出边界)

  2. 然后,一个新士兵出现在主基地 k(这个新士兵不受当天命令影响)

目标

给定:

  • n: 基地总数

  • m: 天数

  • k: 主基地位置

如果一个基地至少有 1 个士兵,则称其为"加固基地"(fortified)。求第 m 天结束时最多能有多少个加固基地。

输入输出

输入: 多组测试数据,每组包含三个整数 n, m, k (1≤k≤n≤10⁵, 1≤m≤10⁹)

输出: 每组数据输出第 m 天结束时最多能加固的基地数量

限制

  • 1 ≤ t ≤ 10⁴ (测试用例数)

  • 1 ≤ k ≤ n ≤ 10⁵

  • 1 ≤ m ≤ 10⁹

  • 所有测试用例的 n 之和不超过 2×10⁵

https://generals.io/ 应该是从这个游戏获取的灵感。

思路讲解

其实就是贪心,不过我们一开始写的贪心代码太屎山了,过不去,我重构了一下,写成了一个二分答案的代码,一次就过了。

那么二分答案,我们已经知道了这个需要占据 occupiedoccupied 个位置了,还有什么变量是不确定的吗?当然是有的,就是左边要占据多少个位置(needlneedl),右边要占据多少个位置(needrneedr)。 不过为了方便期间,我们定义左边为空比较少的(remlreml),右边为空比较多的(remrremr),即 remr>remlremr>reml。使用以下式子计算出 needlneedlneedrneedr

reml×2<occupied:needlreml,needroccupiedneedlothers:needloccupied2,needroccupiedneedlreml\times2<occupied:needl\gets reml,needr\gets occupied-needl\\ \text{others}:needl\gets\frac{occupied}{2},needr\gets occupied-needl

那么,我们已经知道了 needlneedlneedrneedr,那我们知道,最好的走法就是先走 needrneedr,然后再走 needlneedl,由于 needrneedlneedr≥needl,我们可以保证走左边的时候出兵点有足够的兵。因此,总的消耗为:

consumeneedl+needr+(needr1)consume\gets needl+needr+(needr-1)

那么最后 consumeconsumemm 比较一下就好了,那么 check 函数写出来了,整个二分答案就没有难点了。

⚠️注意:我们对传入的参数 occupiedoccupied 减了 1,因为出兵点是自动占领的嘛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
auto check=[&](ll occupied) {
if (occupied>N) return false;
occupied--;
ll reml=max(0ll,K-1),remr=max(0ll,N-K);
if (reml>remr) {
swap(reml,remr);
}
ll needl=0,needr=0;
if (reml*2<occupied) {
needl=reml;
needr=occupied-reml;
}else {
needl=occupied/2;
needr=occupied-needl;
}
ll consume=needl+needr*2-1;
if (consume>M) {
return false;
}
return true;
};

AC代码

AC
https://codeforces.com/contest/2183/submission/358159596

心路历程(WA,TLE,MLE……)