ABC462-①排序②差分③数学④带权区间选择 DP

3054 字
15 分钟
ABC462-①排序②差分③数学④带权区间选择 DP

ABC462#

CodeQUEEN 2026 -qual- (AtCoder Beginner Contest 462) - AtCoder

更好的阅读体验

C-排序#

题意:

给定二维平面上N个点,求满足以下条件点的个数:

以平行于xy轴的矩形,其左下顶点为(0,0),右上为(x,y),该矩形内不包含编号1到N的任意一点

题解:

按照x,y坐标形式升序,枚举每个点并维护当前点的最小y值,若

一个点合法当且仅当其左侧不存在纵坐标更小的点,即 pi是前缀最小值,

n = int(input())
a = []
for i in range(n):
x, y = map(int, input().split())
a.append((x, y))
a.sort()
ans = 0
min_y = n + 1
for x, y in a:
if y < min_y:
ans += 1
min_y = y
print(ans)

D-差分#

题目重述

N 个嫌疑人,第 i 个人在时间 S_i 进入别墅,在时间 T_i 离开别墅,期间始终待在别墅中,因此其在别墅中的时间区间为:

[Si,Ti][S_i,T_i]

已知:

  • 恰好有两名凶手;
  • 犯罪从整数时刻 x 开始;
  • 持续 D 个时间单位;
  • 在整个犯罪过程
[x,x+D][x,x+D]

内,两名凶手必须始终在别墅中。

要求统计所有合法的:

(凶手A,凶手B,x)(\text{凶手A},\text{凶手B},x)

方案数。

其中两名凶手无序,不同开始时间视为不同方案。


思路分析

对于某个人来说,如果犯罪开始于时间 x,则必须满足:

SixS_i \le x

并且

x+DTix+D \le T_i

整理得:

SixTiDS_i \le x \le T_i-D

因此此人能够参与犯罪的所有开始时间构成区间:

[Si,TiD][S_i,T_i-D]

若:

TiSi<DT_i-S_i<D

则该区间为空,此人不可能参与犯罪。


设:

cxc_x

表示能够在时间 x 作为凶手的人数。

即满足:

SixTiDS_i\le x\le T_i-D

的人数。

那么当开始时间固定为 x 时,可以从这 c_x 个人中任选两人:

(cx2)\binom{c_x}{2}

种选择。

因此答案为:

x(cx2)\sum_x \binom{c_x}{2}

差分统计覆盖次数

每个人贡献一个可行开始时间区间:

[Si,TiD][S_i,T_i-D]

问题变成:

求每个时间点被多少个区间覆盖。

这是经典差分问题。

对于区间:

[L,R][L,R]

执行:

d[L] += 1
d[R + 1] -= 1

这里:

L = S_i
R = T_i - D

因此:

d[S_i] += 1
d[T_i - D + 1] -= 1

随后求前缀和即可得到:

c += d[t]

其中 c 即当前时间点的覆盖人数。


统计答案

对于每个时间点:

ans += c * (c - 1) // 2

因为:

=c(c1)2=\frac{c(c-1)}2

表示当前时间作为犯罪开始时间时,两名凶手的选择方案数。

把所有时间点贡献累加即可。


正确性证明

对于某个时间 x

能够参与犯罪的人恰好满足:

SixTiDS_i \le x \le T_i-D

因此覆盖时间点 x 的区间数量正好等于:

cxc_x

在这些人中任选两人即可组成凶手组合,共有:

(cx2)\binom{c_x}{2}

种方案。

不同时间的方案互不重叠,因此总方案数为:

x(cx2)\sum_x \binom{c_x}{2}

算法统计的正是该式,因此正确。


复杂度分析

时间范围最大为:

10610^6

差分扫描一次即可。

时间复杂度:

O(N+106)O(N+10^6)

空间复杂度:

O(106)O(10^6)

满足题目要求。


将每个人能参与犯罪的开始时间转化为区间 [S_i,T_i-D],利用差分统计每个时间点可作案人数 c,答案即所有时间点的

(c2)\binom{c}{2}

f

之和。

差分
N, D = map(int, input().split())
M = 10**6 + 1
d = [0] * M
for i in range(N):
S, T = map(int, input().split())
if T - S >= D:
d[S] += 1
d[T - D + 1] -= 1
c, ans = 0, 0
for i in range(M):
c += d[i]
ans += c * (c - 1) // 2
print(ans)

E-数学#

题目重述

平面上有一个棋子,初始位于:

(0,0)(0,0)

每次操作可以向上下左右移动一格。

设当前是第 kk 次移动:

  • kk 为奇数:
    • 水平移动费用为 AA
    • 垂直移动费用为 BB
  • kk 为偶数:
    • 水平移动费用为 BB
    • 垂直移动费用为 AA

要求将棋子移动到:

(X,Y)(X,Y)

求最小总代价。

共有 TT 组数据。


性质观察

首先发现方向不重要。

因为:

  • 向左和向右费用相同;
  • 向上和向下费用相同。

因此只关心需要移动多少次横向和纵向。

设:

x=X,y=Yx=|X|,\qquad y=|Y|

那么:

  • 横向必须移动 xx 次;
  • 纵向必须移动 yy 次;

总步数:

n=x+yn=x+y

奇偶步费用规律

观察连续两步:

  • 第一步(奇数步)
  • 第二步(偶数步)

若两步都走水平:

A+BA+B

若两步都走竖直:

A+BA+B

若一横一竖:

A+A=2AA+A=2A

或者

B+B=2BB+B=2B

因此每两步形成一个配对时:

  • 同方向代价固定为 A+BA+B
  • 不同方向代价可能是 2A2A2B2B

于是问题本质变成:

如何安排横步和竖步在奇偶位置上的分布。


不妨设 A ≤ B

首先执行:

if a > b:
a, b = b, a

因此后面始终满足:

ABA\le B

此时:

2AA+B2A \le A+B

说明:

一横一竖组成一对永远不劣于两步同方向。

因此应尽量让奇偶步分别承担不同方向。


偶数总步数情况

先讨论:

x+yx+y

为偶数。

设:

xyx\le y

(代码中交换保证)

if x > y:
x, y = y, x

方案一:全部采用交替

若能做到每对都是:

(H,V)(H,V)

则每对费用:

2A2A

总共有:

yy

对。

总代价:

2Ay2Ay

对应代码:

2 * a * y

方案二:先尽量配对

横向共有 xx 步。

把这 xx 步全部与竖向配对:

x 对x \text{ 对}

费用:

2Ax2Ax

剩余:

yxy-x

个竖向步。

由于总步数为偶数,

yxy-x

也是偶数。

这些剩余竖步只能两两组成同方向对。

每对费用:

A+BA+B

共有:

yx2\frac{y-x}{2}

对。

总代价:

化简:

对应代码:

a * (x + y)
+ (b - a) * (y - x) // 2

因此偶数情况答案为:

min(2Ay,A(x+y)+(BA)(yx)2)\operatorname* {m i n} \left(2 A y , A(x + y) + \frac{(B - A) (y - x)}{2} \right)

奇数总步数情况

若:

x+yx+y

为奇数。

最终一定会剩下一步无法配对。


最后一步只能是:

  • 横向一步
  • 或纵向一步

分别产生代价:

AA

BB

于是枚举最后一步:

最后一步为横向

先完成:

(x1,y)(x-1,y)

此时总步数变为偶数。

费用:

solve(x-1,y)+A

最后一步为纵向

先完成:

(x,y1)(x,y-1)

费用:

solve(x,y-1)+B

取最小值:

min(
solve(x-1,y)+A,
solve(x,y-1)+B
)

算法流程

对于每组数据:

  1. 取绝对值
x = abs(X)
y = abs(Y)
  1. 若总步数为偶数

直接调用:

solve(a,b,x,y)
  1. 若总步数为奇数

枚举最后一步:

min(
solve(a,b,x-1,y)+a,
solve(a,b,x,y-1)+b
)

正确性证明

设需要横向移动 xx 次,纵向移动 yy 次。

由于费用只与:

  • 当前步数奇偶
  • 移动方向

有关,而与具体坐标无关,因此问题只取决于横步和竖步的排列顺序。

ABA\le B 时,

2AA+B2A\le A+B

说明让一横一竖占据一组奇偶位置一定不劣于同方向配对。

因此最优策略一定是:

  • 尽可能进行横竖配对;
  • 无法配对的部分再使用同方向配对;

从而得到偶数情况的公式。

奇数情况必然存在一个未配对的最后一步,枚举其方向即可覆盖全部最优方案。

故算法正确。


复杂度分析

每组数据仅进行常数次运算:

O(1)O(1)

总复杂度:

O(T)O(T)

其中:

T2×105T\le 2\times10^5

可以轻松通过。


把移动次数转化为横步数和竖步数,利用「奇偶两步构成一组」的性质分析配对代价;当 ABA\le B 时优先进行横竖配对,得到偶数步公式,奇数步再枚举最后一步方向即可在 O(1)O(1) 时间求解。

数学
input = __import__("sys").stdin.readline
def solve(a, b, x, y):
assert (x + y) % 2 == 0
if a > b:
a, b = b, a
if x > y:
x, y = y, x
return min(2 * a * y, a * (x + y) + (b - a) * (y - x) // 2)
for _ in range(int(input())):
a, b, x, y = map(int, input().split())
x, y = abs(x), abs(y)
if (x + y) % 2 == 1:
print(min(solve(a, b, x - 1, y) + a, solve(a, b, x, y - 1) + b))
else:
print(solve(a, b, x, y))

F-带权区间选择 DP#

题目重述

给定一个仅由大写字母组成的字符串 S

记字符串中 "ABC" 作为连续子串出现的次数为:

cnt(S)cnt(S)

现在允许修改字符串中的若干字符。

目标是使修改后的字符串满足:

cnt(S)=cnt(S)+Kcnt(S') = cnt(S) + K

"ABC" 的出现次数恰好增加 K 次。

要求:

  • 判断是否存在这样的修改方案;
  • 如果存在,求最少需要修改多少个字符;
  • 否则输出 -1

题解思路

一次新增的 "ABC" 必然对应一个长度为 3 的区间:

ABC

设其结尾位置为 i,对应区间:

[i2,i][i-2,i]

对于每个长度为 3 的区间,可以计算:

yiy_i

表示把该区间修改成 "ABC" 所需的最少修改次数。

例如:

ABC -> 0
AAC -> 1
BBB -> 2
CCC -> 3

代码中的计算:

if(s[i-3]!='A') y[i]++;
if(s[i-2]!='B') y[i]++;
if(s[i-1]!='C') y[i]++;

正是在求这个值。


原有 ABC 的处理

定义:

zi={1区间[i2,i]本来就是 ABC0否则z_i = \begin{cases} 1 & \text{区间} [i-2, i] \text{本来就是 ABC} \\ 0 & \text{否则} \end{cases}

即:

if(y[i]==0) z[i]++;

假设我们选择某个区间作为新增的 "ABC"

那么最终该区间一定会变成 "ABC"

此时会产生两个影响:

  1. 新增一个 "ABC"
  2. 可能覆盖原来已经存在的 "ABC"

x 数组的含义

注意两个 "ABC" 只有距离小于 3 时才会重叠。

因此对于一个原有 "ABC"

ABC

它只会影响:

位置 i
位置 i+1
位置 i+2

对应代码:

for(int j=0;j<3;j++){
if(i+j<=n)x[i+j]+=z[i];
}

因此:

xix_i

表示:

若把区间 ii 变成新的 "ABC",会覆盖掉多少个原本存在的 "ABC"


DP 状态设计

定义:

dp[i][j]dp[i][j]

表示:

处理前 ii 个字符后,

最终需要净增加 jj"ABC"

所需的最小修改次数。


状态转移

不选择位置 i 作为新的 ABC

直接跳过当前位置:

dp[i][j]=dp[i-1][j+z[i]]

原因:

若当前位置原本已经有一个 "ABC"

z[i]=1z[i]=1

那么不保留它,相当于净增量减少 1。

对应代码:

if(j+z[i]<=k)
dp[i][j]=min(dp[i][j],dp[i-1][j+z[i]]);

选择位置 i 作为新的 ABC

则必须占据区间:

[i2,i][i-2,i]

为了避免重叠,

前一个选择只能结束在:

i3i-3

之前。

因此转移来自:

dp[i-3][...]

新增一个 "ABC" 后:

贡献:

+1+1

但会覆盖:

x[i]x[i]

个原有 "ABC"

因此净贡献:

1x[i]1-x[i]

故状态转移:

=dp[i3][j1+x[i]]= dp[i-3][j-1+x[i]]

对应代码:

dp[i][j]=
min(
dp[i][j],
dp[i-3][j-1+x[i]]+y[i]
);

初始化

dp[0][0]=0

其余:

INF

表示不可达。


最终答案

要求净增加:

KK

"ABC"

因此答案为:

dp[n][K]

若不可达:

-1

正确性理解

由于两个 "ABC" 长度均为 3,

最终字符串中的新增 "ABC" 一定可以看成若干个互不重叠的长度 3 区间。

每个区间:

  • 花费 y[i] 修改成 "ABC"
  • 产生一个新的 "ABC"
  • 可能覆盖 x[i] 个原有 "ABC"

因此每个区间对答案的贡献为:

1x[i]1-x[i]

问题转化成:

在所有长度 3 的区间中选择若干互不重叠区间,

使净增量恰好为 KK

修改代价最小。

这正是上述 DP 所求。


复杂度分析

设:

n=Sn=|S|

状态数:

O(nK)O(nK)

其中:

K10K\le 10

因此:

O(nK)=O(10n)O(nK)=O(10n)

总长度:

n3×105\sum n\le 3\times 10^5

总复杂度:

O ⁣(nK)O\!\left(\sum n \cdot K\right)

约为:

3×1063\times10^6

可以轻松通过。


先预处理每个长度为 3 的区间变成 "ABC" 的代价以及其会覆盖多少原有 "ABC",然后把问题转化为「选择若干互不重叠区间,使净增加的 "ABC" 数量恰好为 KK 且总代价最小」的区间 DP。

带权区间选择 DP
import sys
input = sys.stdin.readline
INF = 10**18
T = int(input())
for _ in range(T):
s = input().strip()
k = int(input())
n = len(s)
x = [0] * (n + 1)
y = [0] * (n + 1)
z = [0] * (n + 1)
for i in range(3, n + 1):
if s[i - 3] != 'A':
y[i] += 1
if s[i - 2] != 'B':
y[i] += 1
if s[i - 1] != 'C':
y[i] += 1
if y[i] == 0:
z[i] = 1
for j in range(3):
if i + j <= n:
x[i + j] += z[i]
dp = [[INF] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1, n + 1):
dp[i][0] = 0
for j in range(1, k + 1):
# 不选择以 i 结尾的 ABC
if j + z[i] <= k:
dp[i][j] = min(dp[i][j], dp[i - 1][j + z[i]])
# 选择区间 [i-2, i]
if i >= 3:
prev = j - 1 + x[i]
if 0 <= prev <= k:
dp[i][j] = min(
dp[i][j],
dp[i - 3][prev] + y[i]
)
ans = dp[n][k]
print(-1 if ans >= INF else ans)
ABC462-①排序②差分③数学④带权区间选择 DP
http://blog.7a7a68.xyz/posts/abc462-/
作者
Waning
发布于
2026-06-14
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Waning
愿你明日如绚丽之花.
公告
还活着.
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
56
分类
8
标签
20
总字数
125,693
运行时长
0
最后活动
0 天前

目录