2025牛客国庆派对day7 M C 题解怎么写?

摘要:Water exgcd #数学 题目翻译 Walk Alone 感到口渴,想要喝水。他想要恰好喝 (x) 单位的水,但没有合适的量杯。他只有两个水壶,容量分别为 (A) 和 (B)。他发现可以对这两个水壶进行以下操作: 将其中一
Water exgcd #数学 题目翻译 Walk Alone 感到口渴,想要喝水。他想要恰好喝 \(x\) 单位的水,但没有合适的量杯。他只有两个水壶,容量分别为 \(A\) 和 \(B\)。他发现可以对这两个水壶进行以下操作: 将其中一个水壶装满水。 将其中一个水壶中的水全部倒掉。 喝掉其中一个水壶中的全部水。 尽可能多地将水从一个水壶转移到另一个水壶,且不能溢出。形式化地说,如果两个水壶当前分别含有 \(a\) 和 \(b\) 单位的水,则他可以从 \(A\) 向 \(B\) 转移 \(\min(a, B - b)\) 单位的水,或者从 \(B\) 向 \(A\) 转移 \(\min(b, A - a)\) 单位的水。 Walk Alone 希望尽可能少地操作。你能告诉他为了喝到恰好 \(x\) 单位的水所需的最少操作次数吗? 输入描述 输入包含多个测试用例。 第一行包含一个整数 \(T\),表示测试用例的数量。 每个测试用例仅有一行,包含三个整数 \(A\)、\(B\) 和 \(x\),分别表示两个水壶的容量以及他需要喝的水的体积。 输出描述 对于每个测试用例,输出一行一个整数,表示最少操作次数。如果无法喝到恰好 \(x\) 单位的水,输出 \(-1\)。 思路 所有的倒水操作实际上可以转化为: \[Ax+By=C \] 其中\(C\)为需要构造的数字 \(x,y\in Z\) 对于一次\(x\pm 1\),实际上需要两次操作 所以答案可以分类讨论: 当\(x\geq 0\&\&y\geq 0\),\(ans=2(|x|+|y|)_{min}\) 否则\(ans=2(|x|+|y|)_{min}-1\) 这是由于\(x\cdot y<0\),则可以少进行操作一次 对于方程\(Ax+By=gcd(A,B)\),可以通过\(exgcd\)解得特解\(x_{0},y_{0}\) 由此可以获得通解: \[\begin{cases} x=x_{0}+k\cdot \frac{B}{gcd(A,B)}\\ \\ y=y_{0}-k\cdot \frac{A}{gcd(A,B)} \end{cases} \] 也就是说,答案变成了关于\(k\)的函数: \[f(k)=|x|+|y|=|x_{0}+k\cdot \frac{B}{gcd(A,B)}|+|y_{0}-k\cdot \frac{A}{gcd(A,B)}| \] 这是一个形如\(f(k)=|ak+b|+|ck+d|\)的绝对值函数,其最小值必然在\(|ak_{0}+b|=|ck_{0}+d|\)时取到,但由于\(k\in Z\),所以可能在\(\lfloor k_{0} \rfloor,\lceil k_{0} \rceil\)处取得 因此,记录所有可能的\(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\) 棵魔法胡萝卜树。设 \(a_i\) 表示第 \(i\) 棵树上的胡萝卜数量(注意 \(a_i\) 是一个实数)。初始时,所有树上都没有胡萝卜。在每一天,他可以对树执行以下两种操作之一: 1 l r x:浇灌从 \(l\) 到 \(r\)(包括两端)的所有胡萝卜树。之后,对于所有 \(l \leq i \leq r\),\(a_i\) 立即增加 \(x / k\),其中 \(k\) 是所有操作中的一个常数。 2 l r:从所有在 \(l\) 到 \(r\)(包括两端)范围内且至少有一个胡萝卜的树上各采摘一个胡萝卜。之后,对于所有满足 \(l \leq i \leq r\) 且 \(a_i \geq 1\) 的整数 \(i\),\(a_i\) 减少 \(1\)。 经过 \(m\) 天后,Walk Alone 采摘了许多胡萝卜,他希望你能告诉他总共收获了多少胡萝卜。 输入描述 第一行包含三个整数 \(n\) \((1 \leq n \leq 10^6)\)、\(m\) \((1 \leq m \leq 2 \cdot 10^5)\) 和 \(k\) \((1 \leq k \leq 10^9)\),分别表示胡萝卜树的数量、操作的数量以及第一种操作中的常数。 接下来的 \(m\) 行每行表示一个操作,格式如上所述。保证 \(1 \leq l \leq r \leq n\) 且 \(1 \leq x \leq 10^9\)。 输出描述 输出一个整数,表示总共收获的胡萝卜数量。 思路 赛时写了个暴力的线段树,维护区间的最大值与最小值,每次取萝卜的时候先判断最小值与\(k\)的大小,再决定是否往下二分 但是这样的写法通过\(80\%\)就\(TLE\)了 这是因为,最极限的情况时,不能取萝卜的位置与能取萝卜的位置交替出现,此时线段树\(query\)操作复杂度来到了\(o(n)\) 实际上,本题的输出内容值得考量:只询问所有操作结束后收到萝卜的总和 首先,将操作1的增加\(\frac{x}{k}\)改为\(x\),操作二的\(-1\)改为\(-k\),这样一来所有的数字都是整数 考虑单个位置\(pos\)的萝卜数量\(c\)变化情况: 如果不加入\(a_{i}\geq k\)的限制,那么在第\(i\)次操作后,萝卜数量\(c_{i}\)可能会变成负数 记第\(pos\)位置被收了\(d\)次萝卜(不考虑限制),则真正收到的萝卜需要考虑上被多收的部分,这一部分即为\(|min\{ c_{i} \}|\) ,再考虑\(k\)的问题,多收的萝卜即为\(\left\lceil \frac{|min\{ c_{i} \}|}{k} \right\rceil\) 因此,当前位置总共收的萝卜即为\(d-\left\lceil \frac{|min\{ c_{i} \}|}{k} \right\rceil\) 接下来就是如何求得所有位置\(pos\)在不考虑限制时的最小萝卜数 维护最小值可以想到使用线段树,而要维护\(n\)个位置的最小值,考虑在\(1\sim n\)上打差分 使用扫描线+线段树遍历\(1\sim n\),每扫过一个\(pos\),让扫描线上的线段树执行\(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; }