2025牛客国庆集训派对day7 M C 个人题解 - 教程
Water
#exgcd #数学
题目翻译
Walk Alone 感到口渴,想要喝水。他想要恰好喝 x x x 单位的水,但没有合适的量杯。他只有两个水壶,容量分别为 A A A 和 B B B。他发现可以对这两个水壶进行以下操作:
- 将其中一个水壶装满水。
- 将其中一个水壶中的水全部倒掉。
- 喝掉其中一个水壶中的全部水。
- 尽可能多地将水从一个水壶转移到另一个水壶,且不能溢出。形式化地说,如果两个水壶当前分别含有 a a a 和 b b b 单位的水,则他可以从 A A A 向 B B B 转移 min ( a , B − b ) \min(a, B - b) min(a,B−b) 单位的水,或者从 B B B 向 A A A 转移 min ( b , A − a ) \min(b, A - a) min(b,A−a) 单位的水。
Walk Alone 希望尽可能少地操作。你能告诉他为了喝到恰好 x x x 单位的水所需的最少操作次数吗?
输入描述
输入包含多个测试用例。
第一行包含一个整数
T
T
T,表示测试用例的数量。
每个测试用例仅有一行,包含三个整数
A
A
A、
B
B
B 和
x
x
x,分别表示两个水壶的容量以及他需要喝的水的体积。
输出描述
对于每个测试用例,输出一行一个整数,表示最少操作次数。如果无法喝到恰好 x x x 单位的水,输出 − 1 -1 −1。
思路
所有的倒水操作实际上可以转化为:
A
x
+
B
y
=
C
Ax+By=C
Ax+By=C
- 其中 C C C为需要构造的数字
- x , y ∈ Z x,y\in Z x,y∈Z
对于一次
x
±
1
x\pm 1
x±1,实际上需要两次操作
所以答案可以分类讨论:
- 当 x ≥ 0 & & y ≥ 0 x\geq 0\&\&y\geq 0 x≥0&&y≥0, a n s = 2 ( ∣ x ∣ + ∣ y ∣ ) m i n ans=2(|x|+|y|)_{min} ans=2(∣x∣+∣y∣)min
- 否则
a
n
s
=
2
(
∣
x
∣
+
∣
y
∣
)
m
i
n
−
1
ans=2(|x|+|y|)_{min}-1
ans=2(∣x∣+∣y∣)min−1
- 这是由于 x ⋅ y < 0 x\cdot y<0 x⋅y<0,则可以少进行操作一次
对于方程
A
x
+
B
y
=
g
c
d
(
A
,
B
)
Ax+By=gcd(A,B)
Ax+By=gcd(A,B),可以通过
e
x
g
c
d
exgcd
exgcd解得特解
x
0
,
y
0
x_{0},y_{0}
x0,y0
由此可以获得通解:
{
x
=
x
0
+
k
⋅
B
g
c
d
(
A
,
B
)
y
=
y
0
−
k
⋅
A
g
c
d
(
A
,
B
)
\begin{cases} x=x_{0}+k\cdot \frac{B}{gcd(A,B)}\\ \\ y=y_{0}-k\cdot \frac{A}{gcd(A,B)} \end{cases}
⎩⎨⎧x=x0+k⋅gcd(A,B)By=y0−k⋅gcd(A,B)A
也就是说,答案变成了关于
k
k
k的函数:
f
(
k
)
=
∣
x
∣
+
∣
y
∣
=
∣
x
0
+
k
⋅
B
g
c
d
(
A
,
B
)
∣
+
∣
y
0
−
k
⋅
A
g
c
d
(
A
,
B
)
∣
f(k)=|x|+|y|=|x_{0}+k\cdot \frac{B}{gcd(A,B)}|+|y_{0}-k\cdot \frac{A}{gcd(A,B)}|
f(k)=∣x∣+∣y∣=∣x0+k⋅gcd(A,B)B∣+∣y0−k⋅gcd(A,B)A∣
这是一个形如
f
(
k
)
=
∣
a
k
+
b
∣
+
∣
c
k
+
d
∣
f(k)=|ak+b|+|ck+d|
f(k)=∣ak+b∣+∣ck+d∣的绝对值函数,其最小值必然在
∣
a
k
0
+
b
∣
=
∣
c
k
0
+
d
∣
|ak_{0}+b|=|ck_{0}+d|
∣ak0+b∣=∣ck0+d∣时取到,但由于
k
∈
Z
k\in Z
k∈Z,所以可能在
⌊
k
0
⌋
,
⌈
k
0
⌉
\lfloor k_{0} \rfloor,\lceil k_{0} \rceil
⌊k0⌋,⌈k0⌉处取得
因此,记录所有可能的 k k k值,遍历一遍取最小的答案即可
代码
#include<iostream>
#include<vector>
#include<unordered_map>
#include<map>
#include<unordered_set>
#include<set>
#include<algorithm>
#include<cmath>
using namespace std;
const double eps = 1e-6;
#define ll long long
#define int ll
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define see(stl) for(auto&ele:stl)cout<<ele<<" ";cout<<'\n';
#pragma GCC optimize(3, "Ofast", "inline")
const int inf = 2e14;
constexpr int exgcd(int a, int b, int& x, int& y) {
if (!b) {
x = 1, y = 0;return a;
}
int g = exgcd(b, a % b, y, x);
y -= a / b * x;
return g;
}
void solve() {
int A, B, C;cin >> A >> B >> C;
int g = __gcd(A, B);
if (C % g != 0) {
cout << -1 << '\n';return;
}
vector<int>v = { (int)ceil(1.0 * C / (A + 1)),(int)floor(1.0 * C / (A + 1)),(int)ceil(1.0 * C / (A - 1)),(int)floor(1.0 * C / (A - 1)) };
int ans = inf;
for (int ele : v) {
rep(d, 0, 10) {
int x = ele - 5 + d;
if ((-A * x + C) % B != 0)continue;
int y = (-A * x + C) / B;
if (x >= 0 && y >= 0)ans = min(ans, 2 * (x + y));
else ans = min(ans, 2 * abs(x - y) - 1);
}
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--)solve();
return 0;
}
Carrot Trees
#线段树 #扫描线 #差分
题目翻译
Walk Alone 在一排中种植了 n n n 棵魔法胡萝卜树。设 a i a_i ai 表示第 i i i 棵树上的胡萝卜数量(注意 a i a_i ai 是一个实数)。初始时,所有树上都没有胡萝卜。在每一天,他可以对树执行以下两种操作之一:
1 l r x:浇灌从 l l l 到 r r r(包括两端)的所有胡萝卜树。之后,对于所有 l ≤ i ≤ r l \leq i \leq r l≤i≤r, a i a_i ai 立即增加 x / k x / k x/k,其中 k k k 是所有操作中的一个常数。2 l r:从所有在 l l l 到 r r r(包括两端)范围内且至少有一个胡萝卜的树上各采摘一个胡萝卜。之后,对于所有满足 l ≤ i ≤ r l \leq i \leq r l≤i≤r 且 a i ≥ 1 a_i \geq 1 ai≥1 的整数 i i i, a i a_i ai 减少 1 1 1。
经过 m m m 天后,Walk Alone 采摘了许多胡萝卜,他希望你能告诉他总共收获了多少胡萝卜。
输入描述
第一行包含三个整数 n n n ( 1 ≤ n ≤ 1 0 6 ) (1 \leq n \leq 10^6) (1≤n≤106)、 m m m ( 1 ≤ m ≤ 2 ⋅ 1 0 5 ) (1 \leq m \leq 2 \cdot 10^5) (1≤m≤2⋅105) 和 k k k ( 1 ≤ k ≤ 1 0 9 ) (1 \leq k \leq 10^9) (1≤k≤109),分别表示胡萝卜树的数量、操作的数量以及第一种操作中的常数。
接下来的 m m m 行每行表示一个操作,格式如上所述。保证 1 ≤ l ≤ r ≤ n 1 \leq l \leq r \leq n 1≤l≤r≤n 且 1 ≤ x ≤ 1 0 9 1 \leq x \leq 10^9 1≤x≤109。
输出描述
输出一个整数,表示总共收获的胡萝卜数量。
思路
赛时写了个暴力的线段树,维护区间的最大值与最小值,每次取萝卜的时候先判断最小值与 k k k的大小,再决定是否往下二分
但是这样的写法通过 80 % 80\% 80%就 T L E TLE TLE了
这是因为,最极限的情况时,不能取萝卜的位置与能取萝卜的位置交替出现,此时线段树 q u e r y query query操作复杂度来到了 o ( n ) o(n) o(n)
实际上,本题的输出内容值得考量:只询问所有操作结束后收到萝卜的总和
首先,将操作1的增加 x k \frac{x}{k} kx改为 x x x,操作二的 − 1 -1 −1改为 − k -k −k,这样一来所有的数字都是整数
考虑单个位置
p
o
s
pos
pos的萝卜数量
c
c
c变化情况:
如果不加入
a
i
≥
k
a_{i}\geq k
ai≥k的限制,那么在第
i
i
i次操作后,萝卜数量
c
i
c_{i}
ci可能会变成负数
记第
p
o
s
pos
pos位置被收了
d
d
d次萝卜(不考虑限制),则真正收到的萝卜需要考虑上被多收的部分,这一部分即为
∣
m
i
n
{
c
i
}
∣
|min\{ c_{i} \}|
∣min{ci}∣ ,再考虑
k
k
k的问题,多收的萝卜即为
⌈
∣
m
i
n
{
c
i
}
∣
k
⌉
\left\lceil \frac{|min\{ c_{i} \}|}{k} \right\rceil
⌈k∣min{ci}∣⌉
因此,当前位置总共收的萝卜即为
d
−
⌈
∣
m
i
n
{
c
i
}
∣
k
⌉
d-\left\lceil \frac{|min\{ c_{i} \}|}{k} \right\rceil
d−⌈k∣min{ci}∣⌉
接下来就是如何求得所有位置 p o s pos pos在不考虑限制时的最小萝卜数
维护最小值可以想到使用线段树,而要维护
n
n
n个位置的最小值,考虑在
1
∼
n
1\sim n
1∼n上打差分
使用扫描线+线段树遍历
1
∼
n
1\sim n
1∼n,每扫过一个
p
o
s
pos
pos,让扫描线上的线段树执行
p
o
s
pos
pos上的所有操作,随后调用根节点即可获取当前
p
o
s
pos
pos的最小萝卜数
代码
#include<iostream>
#include<vector>
#include<unordered_map>
#include<map>
#include<unordered_set>
#include<set>
#include<algorithm>
#include<cmath>
using namespace std;
const double eps = 1e-6;
#define ll long long
#define int ll
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define see(stl) for(auto&ele:stl)cout<<ele<<" ";cout<<'\n';
#pragma GCC optimize(3, "Ofast", "inline")
const int N = 1e6 + 5;
int n, m, k;
#define ls p<<1
#define rs p<<1|1
#define mid (l+r>>1)
int mi[N << 2], add[N << 2];
void pushup(int p) {
mi[p] = min(mi[ls], mi[rs]);
}
void pushdown(int p, int l, int r) {
if (add[p]) {
mi[ls] += add[p];mi[rs] += add[p];
add[ls] += add[p];add[rs] += add[p];
add[p] = 0;
}
}
void update(int p, int l, int r, int x, int y, int val) {
if (x <= l && r <= y) { mi[p] += val;add[p] += val; return; }
pushdown(p, l, r);
if (x <= mid)update(ls, l, mid, x, y, val);
if (y > mid)update(rs, mid + 1, r, x, y, val);
pushup(p);
}
struct que {
int op, l, r, x;
};
vector<que>q[N];
void solve() {
cin >> n >> m >> k;
rep(i, 1, m) {
int op;cin >> op;
if (op == 1) {
int l, r, x;cin >> l >> r >> x;
q[l].push_back({ 1,i,m,x });
q[r + 1].push_back({ 1,i,m,-x });
} else {
int l, r;cin >> l >> r;
q[l].push_back({ 2,i,m,-k });
q[r + 1].push_back({ 2,i,m,k });
}
}
int ans = 0, d = 0;
rep(i, 1, n) {
for (auto [op, l, r, x] : q[i]) {
if (op == 1) {
update(1, 1, m, l, r, x);
} else {
if (x < 0)d++;
else d--;
update(1, 1, m, l, r, x);
}
}
int del = -min(0ll, mi[1]);
ans += d - (del + k - 1) / k;
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
//cin >> t;
while (t--)solve();
return 0;
}

浙公网安备 33010602011771号