题解:
首先考虑到原题是一个环(因为到m之后又会回到1),所以先破环成链,然后在后面把原来的复制一遍。
那么对于每一个操作而言,都相当于从第一段的某个地方,向后走到达第一段or第二段的某个地方,没有回头。
观察到不管我们的特殊按钮是哪个数,对于每次操作的开始和结束点都是不产生影响的,因此我们应该要单独考虑这个特殊按钮对每一组操作的贡献。
那么对于从x到y这组操作,有哪些值为k的特殊按钮可以产生贡献呢?
我们可以分两种情况讨论:
1,x < y
红色区域即为可以减小代价的k值,那么考虑对于每个k值而言,可以减小多少代价呢?
当k = x + 2时,我们可以把2步变成1步,所以减小了代价1,;同理,对于k = x + 3,减小了代价2……以此类推,减小的代价是一个等差数列。
所以我们只需要先差分一次,然后统计2次前缀和就可以实现O(1)的给一段区间加上一个等差数列。
即1 0 0 0 0 0 -1 ----> 1 1 1 1 1 0 ----> 1 2 3 4 5 0
为了在第6个位置把前面的累计贡献完全消除,我们还需要记录一个d[i]表示需要减小多少累积值,具体可以看代码,自己推一推也行。
2,x > y
维护方法和上一种类似,为了方便,直接给红色区域加上贡献,在最后得到整个数组后再统计一次,把k[i] += k[i + m];
然后用最初的代价(不考虑特殊按钮) 减去max(k[i])即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 201000 5 #define LL long long 6 7 int n, m; 8 LL ans; 9 LL s[AC], k[AC], d[AC]; 10 11 inline int read() 12 { 13 int x = 0;char c = getchar(); 14 while(c > '9' || c < '0') c = getchar(); 15 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 16 return x; 17 } 18 19 inline int cal(int l, int r) 20 { 21 if(l == r) return 0; 22 if(l < r) return r - l; 23 else return r + m - l; 24 } 25 26 inline void pre() 27 { 28 n = read(), m = read(); 29 for(R i = 1; i <= n; i ++) 30 { 31 s[i] = read(); 32 if(i > 1) ans += cal(s[i - 1], s[i]);//先计算出没有特殊按钮的代价 33 } 34 } 35 36 inline void add(int l, int r) 37 { 38 //printf("%d %d\n", l, r); 39 if(l > r) return ; 40 ++ k[l], -- k[r + 1], d[r + 1] -= r - l + 1;//是要在后面减掉前面累积的 41 } 42 43 inline void upmax(LL &a, LL b) 44 { 45 if(b > a) a = b; 46 } 47 48 void work() 49 { 50 for(R i = 1; i < n; i ++) 51 { 52 if(s[i] == s[i + 1]) continue; 53 if(s[i] < s[i + 1]) add(s[i] + 2, s[i + 1]); 54 else add(s[i] + 2, s[i + 1] + m); 55 } 56 int b = 2 * m; LL maxn = 0; 57 for(R i = 1; i <= b; i ++) k[i] += k[i - 1]; 58 for(R i = 1; i <= b; i ++) k[i] += k[i - 1] + d[i]; 59 for(R i = 1; i <= m; i ++) k[i] += k[i + m], upmax(maxn, k[i]); 60 printf("%lld\n", ans - maxn); 61 } 62 63 int main() 64 { 65 freopen("in.in", "r", stdin); 66 pre(); 67 work(); 68 fclose(stdin); 69 return 0; 70 }