欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

灯泡开关

程序员文章站 2022-03-06 11:50:02
...

灯泡开关

题目链接:ybtoj 20028

题目大意

就是每次可以将一个数加一或者到达你设定的一个点。
如果加到 m + 1 m+1 m+1 就会变回一。

然后给你一个序列,数原来是序列的第一个数,然后要变换到第二个数,在变成第三个,直到最后一个。

然后要你求最少操作次数。

思路

很容易想到 n 2 n^2 n2 做法,就是枚举设定的点。

但是怎么优化呢?
考虑一开始不设点,然后设 f i f_i fi 为设定的点设在 i i i 会使答案减少多少步。

那我们看,如果要从 x x x 走到 y y y,我们就来分类讨论。
如果你设的点是 x x x 或者 x + 1 x+1 x+1(如果加了超过 m m m 自动 − m -m m,下同),那直接走会设跳了再走一样,甚至更优。
那如果设的点 x + 2 x+2 x+2 或者之后,那就会分别省 1 , 2 , 3 , . . . 1,2,3,... 1,2,3,... 步,以此类推。

那要让一个序列加上一个等差序列,很容易就想到要用双重差分。
第一次,变成 1 , 1 , 1 , . . . , − ( 前 面 1 的 个 数 ) 1,1,1,...,-(前面1的个数) 1,1,1,...,(1)
第二次,变成这个:
1 , 0 , 0 , . . . , − ( 上 面 的 这 一 个 + 1 ) , ( 前 面 的 这 个 数 − 1 ) 1,0,0,...,-(上面的这一个+1),(前面的这个数-1) 1,0,0,...,(+1),(1)

但是你会发现它可能会断开两节。
那右边那一节还好,左边一节又要重新搞。

假设这里是从 i i i 开始。
那我们就开一个一重的差分数组,在第一位加 i − 1 i-1 i1,在最后一位的后面减回去,就可以变成跟上面一样都从一开始了。

最后把它还原,然后找到减少数量最大的。
然后就算出答案了。

代码

#include<cstdio>
#include<iostream>

using namespace std;

int n, m, a[1000001], sum;
long long ans, an, cf[1000001], cf2[1000001];

long long getdis(int x, int y) {
	if (x < y) return (long long)(y - x);
	return (long long)(y + m - x);
}

void work(int x, int y, int from, int add) {
	cf[x] += (long long)from;
	cf[y + 1] -= (long long)from;
	cf2[x + 1] += (long long)add;
	cf2[y + 1] -= (long long)add * (long long)(y - x + 1);
	cf2[y + 2] += (long long)add * (long long)((y - x + 1) - 1);
}

int main()
{
	freopen("light.in", "r", stdin);
	freopen("light.out", "w", stdout);
	
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	
	for(int i = 2; i <= n; i++) {
		ans += getdis(a[i - 1], a[i]);
		
		if (a[i] > a[i - 1]) {
			if (a[i] - a[i - 1] > 1) work(a[i - 1] + 2, a[i], 1, 1);
		}
		else {
			if (m - a[i - 1] > 1) {
				work(a[i - 1] + 2, m, 1, 1);
				work(1, a[i], m - (a[i - 1] + 1) + 1, 1);
			}
			if (m - a[i - 1] == 1) {
				work(1, a[i], 1, 1);
			}
			if (m == a[i - 1] && a[i] > 1) work(2, a[i], 1, 1);
		}
	}
	
	for (int i = 2; i <= m; i++) {
		cf[i] += cf[i - 1];
		cf2[i] += cf2[i - 1];
	}
	for (int i = 2; i <= m; i++)
		cf2[i] += cf2[i - 1];
	for (int i = 1; i <= m; i++)
		an = max(cf[i] + cf2[i], an);
	
	printf("%lld", ans - an);
	
	return 0;
}

相关标签: # 差分 差分