1. 程式人生 > >p1895 數列操作(差分)問題

p1895 數列操作(差分)問題

題目

描述 Description
給定一個長度為n的數列{a1,a2…an},每次可以選擇一個區間[l,r],使這個區間內的數都加一或者都減一。
問至少需要多少次操作才能使數列中的所有數都一樣,並求出在保證最少次數的前提下,最終得到的數列有多少種。
輸入格式 Input Format
第一行一個正整數n
接下來n行,每行一個整數,第i+1行的整數表示ai。
輸出格式 Output Format
第一行輸出最少操作次數
第二行輸出最終能得到多少種結果
樣例輸入 Sample Input
4
1
1
2
2
樣例輸出 Sample Output
1
2
時間限制 Time Limitation
各個測試點1s
註釋 Hint
對於100%的資料,n=100000,0<=ai<2147483648。

題解

相鄰兩項做差,也就是數列的差分,這樣就把區間修改問題改為單個元素修改問題。
並且做差之後,問題也基本轉換成功。

對於帶有“將一段區間內的每個數全部加上某個值”這種操作的題目,通常考慮差分原數列以簡化情況,將對一段區間的操作轉化為對某兩個特定數的操作。

我們定義d_1 = a_1, d_i = a_i - a_{i-1} ( 2 ≤ i ≤ n ), d_{n+1} = 0(事實上,稍後我們會看到d_1和d_{n+1}的值並不重要),可以發現,原題中的“將[l,r]內的數都加一或都減一”將對應“將d_l + 1,將d_{r+1} - 1”(或反之)的操作。顯然,題目中要求的a數列中的所有數全部相等的條件等同於使d_i = 0 ( 2 ≤ i ≤ n ),

最後數列中的數即為d_1,而題目中的操作允許我們把d數列中的某個數+1,某個數-1。要將d數列中第二項至第n項全部變為0並使操作次數最少,

首先我們將每個負數和每個正數配對執行操作,設d數列中第2至第n項所有正數分別求和得到的值為p,負數分別求和得到的值的絕對值為q,

這一步的操作次數即為min{p,q}。此時還剩餘和的絕對值為abs(p-q)的數沒有變為0,
每次操作我們可以將其與d_1或d_{n+1}配對進行操作,操作次數為abs(p-q),

容易看出,最終d_1的可能取值有abs(p-q)+1種。因此,第一問的答案即為max{p,q},第二問的答案即為abs(p-q)+1。

標準題解如上。
哎,說實話吧,OJ上很難得的有這種詳細的題解,看完一遍,我就把程式碼寫出來了,這不是抄啊!畢竟我是第一次做差分題,於是我就無恥地把題解粘過來了。

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;
inline ll read()
{
	ll f=1,num=0;
	char ch=getchar();
	while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
	while (isdigit(ch)) num=(num<<1)+(num<<3)+(ch^48), ch=getchar();
	return num*f;
}
ll a[maxn],d[maxn],sum1,sum2;
int main()
{
	memset(a,0,sizeof(a));
	ll n=read();
	for (int i=1;i<=n;++i)
	{
		a[i]=read();
		d[i]+=a[i]-a[i-1];
	}
	for (int i=2;i<=n;++i)
	{
		if (d[i]>0) sum1+=d[i];
		else sum2+=abs(d[i]);
	}
	printf("%lld\n",max(sum1,sum2));
	printf("%lld\n",abs(sum1-sum2)+1);
	return 0;
}