题解:AT_arc077_c [ARC077E] guruguru
思路
先考虑最简单的情况,从 \(a_i\) 到 \(a_{i+1}\),并且 \(a_{i+1}>a_i\),例如从位置 \(3\) 到位置 \(8\)。易知如果没有红色的按钮的话,就只能使用黑色按钮。则按 \(a_{i+1}-a_i\) 次,就可以从 \(a_i\) 档到 \(a_{i+1}\) 档。
如果有红色按钮,可以一步跳到 \(x\) 位置,则假如 \(x\) 设置在其中间位置,只需按 \(a_{i+1}-x+1\) 次即可完成任务。那么可以将每个 \(a_i\) 和 \(a_{i+1}\) 看成一条线段,只要 \(x\) 设置在线段上,这次操作就有相应的次数减少 \(x-a_i-1\) 次,\(x\) 在线段外即对当前操作无影响。
注意到这个影响量 \(x-a_i-1\) 由两部分组成,其中 \(x\) 是个变化的量,\(a_i+1\) 是个稳定的量。将后者提取出来,设 \(b\) 数组将线段上区间为 \([a_{i-1}+1,a_i]\) 这一段,都加上权值 \(a_i+1\)(这个权值是今后算最终结果时要减去的部分),同时设 \(c\) 数组为在同样线段上各个值 \(+1\),表示 \(x\) 这个位置就能减少次数,最后遍历一下就能知道 \(x\) 设置在位置 \(i\) 能减少的总次数了。
上述操作用差分数组解决,时间复杂度 \(O(n)\)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
#define rep(i, l, r) for(int i = l; i <= r; ++ i)
#define per(i, r, l) for(int i = r; i >= l; -- i)
const int N = 1e5 + 10, INF = 0x3f3f3f3f3f3f3f3f;
int n, m, a[N], ans = INF, c[N], b[N];
void check(int s, int t)
{
b[1] += t - s;
b[t + 1] -= s + 1;
b[s + 1] += s + 1;
++ c[t + 1];
-- c[s + 1];
if(t <= s) b[1] += 1 + s, b[t + 1] += m, -- c[1];
}
main()
{
scanf("%lld %lld", &n, &m);
rep(i, 1, n) scanf("%lld", &a[i]);
rep(i, 1, n - 1) check(a[i], a[i + 1]);
rep(i, 1, m)
{
c[i] += c[i - 1];
b[i] += b[i - 1];
ans = min(ans, c[i] * i + b[i]);
}
printf("%lld", ans);
}