AtCoder Regular Contest 077 E

来源:互联网 发布:淘宝联盟高佣活动入口 编辑:程序博客网 时间:2024/06/05 00:51

题目链接: AtCoder Regular Contest 077 E - guruguru

题目大意

两个按钮, 一个可以使计数器+1(计数器数字从1-m), 当前值为m时, 再+1就变成了1
另一个按钮储存了一个值x, 按一下就从任意值会变成x
n-1次操作, 由一个数组a[n]描述, 第i次操作: 将计数器从a[i]调到a[i+1]
将x设置为某个值, 使得所有操作需要按按钮的次数总和最小, 输出这个最小值
范围:
2n,m105
1aim
aiai+1
n,m,ai

思路

设置一个数组p, p[i] := 如果x在i位置, 对于所有操作, 使用第二个按钮能够减少的操作次数(相对于只使用第一个按钮)
l = a[i], r = a[i+1], 如果l>r, 令r = r+m, 这样就不用考虑上界的问题了
那么对于每一对l, r, 如果r-l<=1, 那么无论x在什么位置, 第二个按钮都不能减少操作次数
如果r-l>1, 那么
x在l+2位置使用第二个按钮能够减少1次操作, 在l+3位置能减少2次… 在r位置能够减少r-(l+2)+1次操作
所以p[l+2] += 1, p[l+3] += 2 ... p[r] += r-(l+2)+1
对每一对l, r如此处理, 得到最后的p数组
设all为只使用第一个按钮所需要的操作总次数
那么`最少操作次数 = all - max{p[i]+p[i+m]}, 1<=i<=m

以上就是基本思路, 如果不加其他优化, 直接写的话, 复杂度O(mn)
能优化的地方是p[l+2] += 1, p[l+3] += 2 ... p[r] += r-(l+2)+1这就是复杂度里m的来源, 可以将它优化到O(1)

如果要将p[l]到p[r]依次加上1, 2, … r-l+1
我们可以这样: p[i] += 1, p[r+1] -= r-l+1 + 1, p[r+2] += r-l+1, 每次只更新这三个值, 最后再从头到尾p[i] += p[i-1]
具体的一组例子, 比如l=2, r=5

更新操作 复杂度 1 2 3 4 5 6 7 更新三个值 O(1) 0 1 0 0 0 -5 4 p[i]+=p[i-1] O(n) 0 1 1 1 1 -4 0 p[i]+=p[i-1] O(n) 0 1 2 3 4 0 0

所以最后总复杂度O(n+m)

参考自: 来源

代码

#include <bits/stdc++.h>using namespace std;const int MAXN = 3E5 + 100;typedef long long ll;int n, m, a[MAXN];ll p[MAXN];int main(){    ll all = 0;    scanf("%d%d", &n, &m);    for(int i=0; i<n; ++i) scanf("%d", a+i);    for(int i=1; i<n; ++i)    {        int l = a[i-1], r = a[i];        if(l>r) r += m;        all += r-l;        if(r-l>1)        {            p[l+2] += 1;            p[r+1] -= (r-(l+2)+1) + 1;            p[r+2] += (r-(l+2)+1);        }    }    for(int i=1; i<=2*m; ++i) p[i] += p[i-1];    for(int i=1; i<=2*m; ++i) p[i] += p[i-1];    ll ans = -1;    for(int i=1; i<=m; ++i) ans = max(ans, p[i]+p[i+m]);    cout << all - ans << endl;    return 0;}