0%

题目大意

背景

有 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……)

题目大意

问题描述

给定长度为 n 的数组 a 和整数 k。需要执行 n−k+1 次操作:

  • 每次操作选择一个长度为 k 的窗口,窗口的 MEX 值在所有大小为 k 的窗口中最大

  • 从这个窗口中删除任意一个元素

  • 重复以上过程直到数组长度变为 k−1

目标: 最大化最终剩余元素的 MEX 值

关键概念

MEX (最小排除值): 集合中未出现的最小非负整数

例如: MEX([1,2,0,1,3,0]) 中窗口 [1,2,0] 的 MEX 是 3

输入输出

输入:

  • 第一行: 测试用例数量 t (1≤t≤10⁴)

  • 每个测试用例第一行: n 和 k (2kn2×102≤k≤n≤2×10⁵)

  • 每个测试用例第二行: n 个整数 a,a,...,aa₁,a₂,...,aₙ (0≤aᵢ≤n)

输出: 一个非负整数,表示可能的最大 MEX 值

思路讲解

哈哈,当你发现一道理应很简单的题目有这个比较复杂的决策的时候,这个时候需要想的是,这个决策是不是根本无关紧要?

哈哈,为什么决策根本无关紧要呢?因为答案肯定是 k1≤k-1 的,因此我们需要的数字是 0k20~k-2,不过我们选择的区间长度是 kk,因此如果区间里面数字全是 [0,k2][0,k-2],那必然有重复数字,删除重复的自然是不会对答案产生影响。如果有数字超过这个范围,那删除也更加没有影响

因此其实最后的这个结果就是原数组 mex 值和 k1k-1 中的最小值,

AC代码

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

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

题目大意

这道题是 Alice 和 Bob 的博弈游戏题。

游戏规则:

  • 两人在一个长度为 n 的数组上进行游戏,数组只包含 0 和 1

  • Alice 先手,双方轮流操作

  • 每次操作选择区间 [l, r],将该区间替换为单个数字 1min(al,...,ar)1 - min(a_l, ..., a_r)

  • 如果区间全是 1,则替换为 0;否则替换为 1

  • 游戏持续到数组只剩一个数字为止

获胜条件:

  • 如果最终剩下的数字是 0,Alice 获胜

  • 否则 Bob 获胜

输入格式:

  • 第一行:测试用例数 t (1 ≤ t ≤ 100)

  • 每个测试用例第一行:数组长度 n (3 ≤ n ≤ 100)

  • 每个测试用例第二行:n 个整数 a_i (0 ≤ a_i ≤ 1)

输出格式:

  • 对于每个测试用例,输出 “Alice” 或 “Bob”(大小写不敏感)

要求: 判断在双方最优策略下谁会获胜

思路讲解

这种题目一般是这样的,看起来这个操作非常复杂,但是实际上是只需要很少轮次的最优操作,就可以得出答案。`

首先,如果整个序列都是11,Alice 就可以通过对整个序列进行操作立即获胜。

注意最后一次操作必须涉及至少一个a1a_1ana_n。我们根据a1a_1ana_n的值进行讨论:

如果a1=1a_1 = 1,那么Alice 可以直接在区间[2,n][2,n]上操作以获胜,因为a2na_{2 \sim n}必须包含至少一个00

如果an=1a_n = 1,那么Alice 可以直接在区间[1,n1][1,n-1]上操作以获胜,因为a1n1a_{1 \sim n-1}必须包含至少一个00

如果a1=0a_1 = 0an=0a_n = 0,那么在整个序列上进行操作肯定会导致Alice 失败。这意味着Alice 不能同时操作a1a_1ana_n。因此,在Alice 操作后,序列aa中仍然至少会有一个00。Bob 然后可以在整个序列上操作以获胜。因此,在这种情况下,Bob 获胜。(这么讲有点奇怪,其实就是,你无论第一次你怎么操作,第二次 Bob 都可以直接掀桌子,得到 1

时间复杂度:O(n)O(\sum n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Solve() {
ll N;
cin >> N;
vector<ll> A(N);
for (int i=0;i<N;++i) {
cin>>A[i];
}
// 全 1 的情况
if (find(all(A),0)==A.end()) {
cout<<"Alice\n";
return;
}
// 只要首尾有一个是 1
if (A.front() || A.back()) {
cout<<"Alice\n";
return;
}
// 首尾都为 0
cout<<"Bob\n";
}

AC代码

AC

https://codeforces.com/contest/2183/submission/358031419

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

题目大意

有 n 个块,编号从 1 到 n,每个块上有一个初始整数 a_i。

对于一个连续段 [l, r],如果整数 d (0 ≤ d ≤ r - l) 满足 min(al,al+1,...,al+d)=dmin(a_l, a_{l+1}, ..., a_{l+d}) = d,则称 d 为 nasty 数字。

一个段的 nastiness 定义为该段中 nasty 数字的数量。

需要处理 q 个操作:

  • **操作 1:**将第 i 个块上的数字修改为 x

  • **操作 2:**查询段 [l, r] 的 nastiness

对于每个操作 2,输出对应段的 nastiness 值。

思路讲解

我们发现一件事情,令 $mn\gets min(a_l, a_{l+1}, …, a_{l+d}) d\uparrowmn\downarrow$,他们两个的这个单调关系是相反的,我们却要求的是 mn=dmn=d 的这个情况的数量,由于 dd 是严格递增的,mnmn 是非严格递减的,这两者显然最多只有一个交点。

那么现在这个问题就来到了,我们实际上就是求一个区间中是否有这个 dd 值嘛,显然,这个可以使用二分解决,只要我们有一个数据结构可以支持这个 RMQ 和这个单点修改即可,可以使用我们的树状数组。

AC代码

AC
https://codeforces.com/contest/2184/submission/358028437

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

题目大意

给定一棵有 n 个顶点的有根树,根节点为 1。每个叶子节点上都有一个樱桃。

你需要通过"摇动"顶点来收集所有樱桃。摇动顶点 v 时,所有作为 v 后代的叶子节点上的樱桃都会掉落。每个叶子的樱桃只能掉落一次,否则树会损坏。

关键限制:摇动的顶点数量必须是 3 的倍数

问题:是否可能在满足上述条件下收集所有樱桃?

输入格式:

  • 第一行:测试用例数 t (1 ≤ t ≤ 10⁴)

  • 每个测试用例:第一行为 n (2 ≤ n ≤ 2×10⁵),接下来 n-1 行描述树的边

**输出格式:**对每个测试用例输出 “YES” 或 “NO”

思路讲解

当你发现一个问题的子结构之间互相重叠覆盖,其中还涉及这个决策的时候,这个时候,你就要想,是不是 dp 了。

树上 dp,定义状态为 dpu,idp_{u,i},指的是以 uu 为根的子树中,是否可以凑出来为这个 i (0,1,2)i\ (0,1,2) 的这个 3 的余数。

接着问题就来到了如何转移?我们不妨定义,toto 为转移过来的子节点,四个你可以理解为 bitset 的东西,长度为 3,bibi 为目前的状态,若 dpto,0dp_{to,0} 为 true,那么 bi1bi1 就是这个 bibi 向前循环移动的结果,否则 bi1bi1 为全 0。同理,我们可以定义出 bi0bi0bi2bi2。最终经过这个 toto 节点的转移后,bibi0bi1bi2bi\gets bi0 \lor bi1 \lor bi2。(当然,第一个 toto 节点转移过来的话,为了初值确定,bidptobi\gets dp_{to}

最后,令 bi1truebi_1\gets \text{true}(因为我们始终可以选择子树根节点,来解决这个问题),接着 dpubidp_u\gets bi

为什么要这么转移?我们会发现,就是说如果子树只有一个唯一的位移的话,是不是只是让这个状态进行一个位移?那么其实只有一个位移是没什么大用的,但是如果有两种位移?三种位移?这几种位移之间如何组合?那么答案就昭然若揭了。

AC代码

AC
https://codeforces.com/contest/2184/submission/357994744

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

image