2023-1018 CSP 模拟赛题解

A. 奶牛杀

题面

题目描述

贝茜和她的朋友们正在玩奶牛杀。在这款游戏中,贝茜本色出演奶牛,她的 nn 个朋友扮演猎人。猎人从 11 到 nn 编号,第 ii 个猎人有血量 aia_i 和攻击力 bib_i。且第 ii 个猎人瞄准 i mod n+1i \bmod n+1 个猎人。当第 ii 个猎人血量小于等于 00时,会立刻向第 i mod n+1i\bmod n+1 个猎人开枪,使之血量减少 bib_i 点。如果它也血量变为了小于等于 00 则也会开枪,有可能造成连锁开枪。

需要注意的是,本游戏并无缩圈环节,若第 i mod n+1i \bmod n+1 个猎人已经退出游戏,ii 号会打空而非向后寻找人开枪。

在奶牛杀中,没有投票环节,只有贝茜每晚上袭击一个猎人,造成 11 点伤害。她现在想知道最快需要几个晚上才能杀死所有猎人?

输入格式

第一行一个正整数 nn表示猎人总数。

接下来的 nn行,第行包含两个正整数 ai,bia_i,b_i,分别表示第 ii个猎人的血量和攻击力。

输出格式

输出一个正整数,表示贝茜最快需要多少个晚上获得胜利。

输入输出样例

样例输入 #1

3
7 15
2 14
5 3

样例输出 #1

6

样例解释 #1

最佳策略是连续 22 个晚上攻击猎人 22,其死亡时的射击导致猎人 33 死亡,猎人 33 死亡时的射击对猎人 11 造成
33 点伤害,接下来只需要连续 44 个晚上攻击猎人 11 即可。

数据范围与约定

  • 对于 10%10\% 的数据,保证 n≤10n\leq 10,
  • 对于另外 10%10\% 的数据,保证 n≤100n\leq 100,
  • 对于另外 20%20\% 的数据,保证 n≤1000n\leq 1000,
  • 对于另外 20%20\% 的数据,保证 bi≤ai mod n+1b_i\leq a_{i\ mod\ n+1},
  • 对于 100%100\% 的数据,保证 n≤3∗105,1≤ai,bi≤1012n\leq 3*10^5,1\leq a_i,b_i\leq 10^{12}。

题意简述

给定 nn 个人,以及其生命值 aia_i,这 nn 个人之间的关系构成一个环,每个人死亡时会对下一个人造成 bib_i 的伤害,你可以选择任意数量的人造成任意伤害,求为了使所有人死亡你所需要造成的伤害

思路

为了方便,我们把每个人的下一个人记为 nxtinxt_i,上一个人记为 preipre_i

假设我们从第 kk 个人开始造成伤害,那么杀掉第一个人所需要的伤害为 aka_k,对于其余所有人,如果他的上一个人可以直接杀掉他,那么我们就不做干预,否则就另外造成伤害

那么显然以第 kk 个人开始的所需要的伤害为:

−max⁡{bprek−ak,0}+ak+∑i=1nmax⁡{bprei−ai,0}-\max{ \{b_{pre_k} - a_k, 0\} } + a_k+ \sum \limits_{i = 1}^{n} \max{ \{b_{pre_i} - a_i, 0\} }

那么计算出 sum=∑i=1nmax⁡{bprei−ai,0}sum = \sum \limits_{i = 1}^{n} \max{ \{b_{pre_i} - a_i, 0\} },枚举每个位置更新答案即可

总的时间复杂度为 O(n)O(n)

代码

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
37
#include <bits/stdc++.h>
namespace solution
{
typedef long long LL;
const int MAXN = 3e5 + 5;
const LL INF = 0x3f3f3f3f3f3f3f3fLL;
int n;
LL sum = 0;
LL ans = INF;
LL a[MAXN], b[MAXN], c[MAXN];
int main()
{
freopen("doge.in", "r", stdin);
freopen("doge.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld%lld", &a[i], &b[i]);
}
for (int i = 1; i <= n; i++)
{
c[i % n + 1] = std::max(0LL, a[i % n + 1] - b[i]);
sum += c[i % n + 1];
}
for (int i = 1; i <= n; i++)
{
ans = std::min(ans, sum - c[i] + a[i]);
}
printf("%lld\n", ans);
return 0;
}
}
int main()
{
solution::main();
return 0;
}

B. 备餐

题面

题目描述

约翰要给他的奶牛们备餐,有 nn种食物,每种食物有满意度 aia_i。约翰将准备食物的一个非空子序列给奶牛。

奶牛们对食物的要求也很高,他们要求从准备的食物中任选两个计算他们满意度的异或,如果小于他们的忍耐度 xx就会生气。

约翰想问问你,在所有 2n−12^n-1 个非空子序列中,有多少种序列满足,“任选两种食物,满意度的异或和大于等于奶牛的忍耐度 xx”?你需要输出方案数模 998244353998244353 之后的结果。

如果子序列只有一个元素,约翰认为一定是合法的。

输入格式

第一行包含两个非负整数 n,xn,x 表示食物的种类数和奶牛们的忍耐度。

第二行包含 nn 个非负整数 a1,a2,...ana_1,a_2,...a_n 表示每种食物的满意度。

输出格式

输出一个非负整数,表示方案数模 998244353998244353 的结果。

输入输出样例

样例输入 #1

3 0 0 1 2

样例输出 #1

7

样例解释 #1

所有的 23−12^3-1 个非空子序列都满足条件

样例输入 #2

3 2 0 1 2

样例输出 #2

5

样例解释 #2

由于 0⨁1=1<20 \bigoplus 1=1<2,子序列 {0,1}\{0,1\} 和子序列 {0,1,2}\{0, 1, 2\} 不满足条件。

样例输入 #3

7 4 11 5 5 8 3 1 3

样例输出 #3

35

样例解释 #3

不保证 aia_i 互不相同,选取不同的 55 或 33,被认为是不同的方案。

数据范围与约定

  • 对于 100%100\% 的数据,保证 1≤n≤3∗1051\leq n \leq 3*10^5。0≤ai,x<2600\leq a_i,x< 2^{60}。

C. 约翰:奶牛杀手

题面

题目描述

约翰最近迷恋上了《只狼:影逝二度》,于是他决定拿奶牛试试手。

具体来说,有 nn个奶牛站在一个数轴上,每个奶牛有一个位置 xix_i,还有一个警戒范围 [li,ri][l_i,r_i]。

刚开始所有奶牛都是未警戒的状态。约翰可以偷袭并瞬间杀死一个未警戒状态的奶牛 ii,但此时所有 xi∈[lj,rj]x_i\in[l_j,r_j] 的奶牛
jj 都会进入警戒状态并前来杀死小W,即满足i的位置位于j的警戒范围内的j。其他奶牛则会保持未警戒状态。

约翰有一次复活机会,复活后仍然活着的奶牛会恢复到未警戒状态和原来的位置。但是如果复活后偷袭被别的奶牛再次被发现就会失败。

约翰有 TT 个农场,他想问问你能否只使用偷袭和复活来通关每张地图。进入每张地图时复活次数会重置为1。

输入格式

输入的第一行包含一个正整数 TT,表示地图总数。

接下来连续输入 TT 张地图的信息。

每张地图的第一行包含一个正整数 nn,表示这张地图中的奶牛总数。

此后的 nn 行,每行包含三个正整数 xi,li,rix_i, l_i, r_i,表示第 ii 头奶牛的位置和警戒范围。保证奶牛按照坐标严格升序给出,即 x1<x2<...<xnx_1 < x_2 < ... < x_n。

输出格式

输出 TT 行,每行包含一个字符串 Yes 或 No,分别表示约翰是否可以通过这张地图。

输入输出样例

样例输入 #1

2
4
3 1 5
6 2 10
7 4 10
9 8 11
3
1 1 3
2 1 3
3 1 3

样例输出 #1

Yes
No

样例解释 #1

对于第一张地图,约翰可以首先对第二个奶牛(位置为 6)发动偷袭,并会被第三个奶牛杀死。在复生后,他可以按照三,一,四的顺序分别杀死剩余的三个奶牛,并且不会引起敌人的警觉。
对于第二张地图,可以发现无论如何安排顺序,约翰都被顺序中最后 的那个敌人杀死两次,用完所有的复生机会。

数据范围与约定

对于 100%100\% 的数据,保证 1≤T≤105,1≤n≤106,∑n≤3∗106,1≤li≤xi≤ri≤109,1≤x1<x2<...<xn≤1091\leq T\leq 10^5,1\leq n \leq 10^6,\sum n\leq 3*10^6,1\leq l_i\leq x_i\leq r_i\leq 10^9,1\leq x_1<x_2<...<x_n\leq 10^9。

题意简述

给定 nn 个区间,每个区间包含三个信息:xi,li,rix_i, l_i, r_i,求判断能否在删除某个区间之后,可以通过每次选择一个“ xix_i 不属于任何其他未删除的区间的 lj,rjl_j, r_j 的区间”删除掉,使得所有区间最后都被删除

暴力做法

简化题意,可以通过相互包含的关系建出一张有向图,图上有环时则无法处理,只能将某个区间删除掉

那么可以通过枚举环上的点删除,拓扑排序判断图中是否还有其他环的方法来解决,时间复杂度为 O(n3)O(n ^ 3)

可以发现如下性质:图中如果存在环,那么必定存在二元环,

以三元环为例,假设图中有三元环 a,b,ca, b, c,不妨设 xa<xb<xcx_a < x_b < x_c,显然 bb 可以在 aa 的区间内,cc 可以在 bb 的区间内,又因为构成环,那么 aa 或 bb 必定有一个在 cc 的区间内,显然无论哪种情况,图中都有二元环,故命题得证

那么现在的问题就变成了在图中找二元环的问题

首先可以在图中找出任意一对二元环,对环上的两个点枚举删除,判断图中是否还有其他二元环

判断二元环时可以从左向右枚举每个点,同时维护一个栈,栈中的元素从栈底到栈顶 xx 递增,rr 递减,xx 递增保证了栈中元素最可能被包含在其他区间中,rr 递减保证了区间中的元素最可能包含其他元素,那么对于每个当前枚举到的元素 ii,一直弹出栈中元素,直到栈中元素的 rr 大于 ii 的 rr 为止,对于弹出的每个元素以及最后的栈顶,判断其与 ii 是否构成二元环,如果枚举完了都没有找到,说明图中不存在二元环 ,也就说明原图为有向无环图

下面考虑证明正确性

假设图中存在二元环 a,ba, b,不妨设 ax<bxa_x < b_x,那么一定有 al≤bx≤ara_l \le b_x \le a_r,分两以下几种情况讨论:

  • 枚举到 bb 时 aa 未被弹出栈, 此时要么枚举到 bb 时与 aa 进行判断,发现 a,ba, b 的环,要么枚举到后边某个位置 cc 时,发现一个更大的环
  • 枚举到 bb 时 aa 已经出栈,说明在前边某个位置 cc,aa 已经和 cc 构成环

故若图中有环,那么该算法一定可以找到

代码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <bits/stdc++.h>
namespace solution
{
typedef long long LL;
const int MAXN = 3e6 + 5;
int t;
LL n;
LL a[MAXN];
struct Node
{
LL l, x, r;
};
Node node[MAXN];
auto find(int pos)
{
std::stack<int> stk;
for (int i = 1; i <= n; i++)
{
if (i == pos)
{
continue;
}
while (!stk.empty())
{
int u = stk.top();
if (node[i].l <= node[u].x && node[u].x <= node[i].r && node[u].l <= node[i].x && node[i].x <= node[u].r)
{
return std::make_pair(i, u);
}
if (node[u].r > node[i].r)
{
break;
}
stk.pop();
}
stk.push(i);
}
return std::make_pair(0, 0);
}
int main()
{
freopen("sekiro.in", "r", stdin);
freopen("sekiro.out", "w", stdout);
scanf("%d", &t);
while (t--)
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld%lld%lld", &node[i].x, &node[i].l, &node[i].r);
}
auto t = find(0);
if (t == std::make_pair(0, 0) || find(t.first) == std::make_pair(0, 0) || find(t.second) == std::make_pair(0, 0))
{
puts("Yes");
}
else
{
puts("No");
}
}
return 0;
}
}
int main()
{
solution::main();
return 0;
}