1 条题解
-
0
自动搬运
来自洛谷,原作者为

Alex_Wei
**搬运于
2025-08-24 21:50:01,当前版本为作者最后更新于2021-12-20 09:09:29,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
- Update on 2022.9.29:修改表述。
- Update on 2025.1.22:修订。
P3543 [POI2012] WYR-Leveling Ground
区间操作转化为差分数组 的端点操作,其中 。一次 和一次 抵消,一次 和一次 抵消。设 ,。
首先考虑对每个差分值单独求解,解不定方程 。设 ,根据裴蜀定理,若 则无解。否则用扩欧求得 的特解,再乘以 得到 的特解。
先不考虑可行性。因为 的贡献为 ,而最终答案为所有贡献之和除以 ,所以先找到 最小的特解。若 和 均不为最小非负整数或最大负整数可行解,则调整法可证 必然不是最优。因此我们只需检查 和 分别取到最小非负整数和最大负整数的情况。
当前 是答案的下界,但不能保证可行性,还需满足 。当 时 ,所以只需考虑 。
根据特解的形式
$$\begin{cases} x = x' + kB \\ y = y' - kA \end{cases} $$当 时,我们需要进行 次将某个 加上 ,并将对应的 减去 。 一定是整数,因为差分数组之和为 。类似的, 时 减去 , 加上 。称这样的操作为对 进行一次调整,调整的代价等于新的 减去原来 。
容易证明调整一个数的代价随着调整次数增加仅在 或 改变符号时增加,其它时候不变,因此我们可以用堆维护这个过程:每次取出代价最小的 并弹出,在需求次数与不改变符号的限制下尽可能多地调整。若经过调整后需求次数为 ,则算法结束,否则将新的调整代价压入堆。时间复杂度 ,因为 中任意一个改变符号才会改变代价,最多发生 次。
一个神奇的性质是 在 级别,这使得我们可以从堆中取数时仅进行一次调整就塞回去。证明(来自 评论区):当 取到最小值时若 为最小非负整数或最大负整数,则 ,则 。若 为最小非负整数或最大负整数则 ,即 ,根据 得 。
忽略问题限制得到基本解法再调整,巧妙结合反悔贪心和扩展欧几里得。
#include <bits/stdc++.h> using namespace std; #define fi first #define se second #define TIME 1e3 * clock() / CLOCKS_PER_SEC using ll = long long; using uint = unsigned int; // using lll = __int128; using pii = pair<int, int>; using pll = pair<ll, ll>; using ull = unsigned long long; inline ll read() { ll x = 0, sgn = 0; char s = getchar(); while(!isdigit(s)) sgn |= s == '-', s = getchar(); while(isdigit(s)) x = x * 10 + s - '0', s = getchar(); return sgn ? -x : x; } inline void print(ll x) { if(x < 0) return putchar('-'), print(-x); if(x >= 10) print(x / 10); putchar(x % 10 + '0'); } bool Mbe; constexpr int N = 1e5 + 5; constexpr ll INF = 0x3f3f3f3f3f3f3f3f; int n, a, b, c[N], d, ad, bd; ll u, v, x[N], y[N], dx, ans; int sgn(ll x) {return x < 0 ? -1 : 1;} void exgcd(ll a, ll b, ll &x, ll &y) { if(!b) return x = 1, y = 0, void(); exgcd(b, a % b, y, x), y -= a / b * x; } priority_queue<pii, vector<pii>, greater<pii>> q; void ins(int i) {q.push({abs(x[i] - sgn(dx) * bd) + abs(y[i] + sgn(dx) * ad) - abs(x[i]) - abs(y[i]), i});} bool Med; int main() { fprintf(stderr, "%.3lf MB\n", (&Mbe - &Med) / 1048576.0); #ifdef ALEX_WEI FILE* IN = freopen("1.in", "r", stdin); FILE* OUT = freopen("1.out", "w", stdout); #endif cin >> n >> a >> b, d = __gcd(a, b); ad = a / d, bd = b / d, exgcd(a, b, u, v); for(int i = 1; i <= n; i++) c[i] = read(); for(int i = ++n; i; i--) c[i] -= c[i - 1], x[i] = INF; for(int i = 1; i <= n; i++) { if(c[i] % d) puts("-1"), exit(0); ll p = u * c[i] / d, q = v * c[i] / d, _x, _y; auto chk = [&]() {if(abs(_x) + abs(_y) < abs(x[i]) + abs(y[i])) x[i] = _x, y[i] = _y;}; _x = (p % bd + bd) % bd, _y = (c[i] - _x * a) / b, chk(), _x -= bd, _y += ad, chk(); _y = (q % ad + ad) % ad, _x = (c[i] - _y * b) / a, chk(), _y -= ad, _x += bd, chk(); dx += x[i], ans += abs(x[i]) + abs(y[i]); } for(int i = 1; i <= n; i++) ins(i); for(ll i = 0; i < abs(dx); i += bd) { pii t = q.top(); q.pop(); ans += t.fi, x[t.se] -= sgn(dx) * bd, y[t.se] += sgn(dx) * ad, ins(t.se); } cout << ans / 2 << "\n"; cerr << TIME << " ms\n"; return 0; } /* 2022/9/29 author: Alex_Wei start coding at 19:23 finish debugging at 20:07 */
- 1
信息
- ID
- 2618
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者