Skip to content

Latest commit

 

History

History
105 lines (79 loc) · 3.45 KB

File metadata and controls

105 lines (79 loc) · 3.45 KB

P4552 [Poetize6] IncDec Sequence

原题地址

题目描述

给定一个长度为n的数列a1, a2, ... , an, 每次可以选择一个区间[l,r],使这个区间内的数都加1或者都减1。

请问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列有多少种。

输入格式

第一行一个正整数n 接下来n行,每行一个整数,第i+1行的整数表示ai 。

输出格式

第一行输出最少操作次数 第二行输出最终能得到多少种结果

输入输出样例

输入#1

4
1
1
2
2

输出#1

1
2

说明/提示

对于100%的数据,n <= 100000,0 <= ai <= 2147483648。

思路

考虑原数组a(长度为n,下标0到n - 1)的差分数组d:

  • d[0] = a[0]
  • d[i] = a[i] - a[i - 1],i属于[1, n - 1]

那么:

  • 若数组a的所有元素相同,那么数组d除d[0]外所有元素为0,而d[0]的值就是a的每个元素的值。
  • 若数组a在[l, r]区间(l <= r < n - 1)内每个数增加1,则d[l]增加1,而d[r + 1]减少1
  • 若数组a在[l, n - 1]区间(l <= n - 1)内每个数增加1,则d[l]增加1,其余不变(n - 1 + 1 = n,d[n]已越界)
  • 若数组a在[l, r]区间(l <= r < n - 1)内每个数减少1,则d[l]减少1,而d[r + 1]增加1
  • 若数组a在[l, n - 1]区间(l <= n - 1)内每个数减少1,则d[l]减少1,其余不变

到这里,原题可以转化为:

对原数组a的差分数组d进行以下3种操作

  • d[i]增加1且d[j]减少1,i <= n - 1 && j <= n - 1 && i != j (一增一减)
  • d[i]增加1,i <= n - 1 (单独增加)
  • d[i]减少1,i <= n - 1 (单独减少)

使得数组d除d[0]外所有元素为0。求最少的操作次数,以及在保证最少次数的前提下,最终得到的数组d中,d[0]有多少种不同的值~

那么如何操作呢
由于d[0]不用转换为0,所以对于i属于[1, n - 1]:
若d[i] > 0,则要将其减少为0,可以使用多次d[i]减少1的操作,但是题目要求操作次数最少,所以若存在j != 0使得d[j] < 0,就可以使用d[i]减少1而d[j]增加1的操作,这样比单独让d[i]减少效率更高。
d[i] < 0时同理。
也就是对于每个d[i],只要它不等于0,就要找d中除d[0]外与d[i]符号相反的数,这样一次操作可以使两个数更接近0;只有在不存在符号相反的数时,才使用单独一个数增加/减少的操作。

那么编程的时候,需要对每个d[i]去找符号相反的数吗,如果这样写,就会开心的TLE了。实际上,我只需要计算出数组d除d[0]外所有元素中,正数之和与负数的绝对值之和,正数之和设为positive,负数绝对值之和设为negative,由于所有正数都需要找负数,而所有负数都要找正数,设positive和negative中大的为max,小的为min,那么max就是所求最少操作次数,其中min次操作为一增一减,max - min次操作为单独增加/减少

还有一个问题就是d[0]可以有多少种不同的值。我们上面的操作都是不针对d[0]的,因为它不用变为0嘛,但是对于所有的单独一个数增加/减少的操作,可以有一种代替的操作,即:
d[i]增加1,可以通过d[0]减少1且d[i]增加1代替,唯一的不同是d[0]减少了1,而d[0]变成多少对最少操作次数没有影响!
同理d[i]减少1,可以通过d[0]增加1且d[i]减少1代替。
而每一个单独一个数增加/减少的操作,如果它像这样被代替了,那么d[0]就会增加1或者减少1。显然,有多少个单独增加/减少操作,d[0]就多了多少种可能。由前面已知,有max- min个单独增加/减少操作,那么d[0]有max - min + 1种不同的值

代码

#include "stdio.h"
#include "stdlib.h"

typedef long long LL;

int main() {
	int n;
	scanf("%d", &n);
	LL  d, pre, a;
	LL positive = 0, negative = 0;
	int i;
	for (i = 0; i < n; i++) {
		scanf("%lld", &a);
		//求差分
		//这里d没有使用数组,第i次循环的d表示d[i]
		if (i) d = a - pre;
		else d = a;
		pre = a;
		if (i) {
		//计算除d[0]外正数之和与负数绝对值之和
			d > 0 ? (positive += d) : (negative -= d);
		}
	}
	LL max = positive > negative ? positive : negative;
	LL min = positive > negative ? negative : positive;
	//最少max次操作,max - min次单独增加/减少操作,d[0]有max - min + 1种不同的值
	printf("%lld\n%lld", max, max - min + 1);
	return 0;
}