刷题笔记:P4452 [差分]

发布时间 2023-05-17 17:23:02作者: SXqwq

题目传送门:https://www.luogu.com.cn/problem/P4552

一道非常巧妙的差分。

我们先来讲一下样例:
原数组:1 1 2 2
差分后:1 0 1 0
这时,我们发现,若满足数组中所有数都相等, 则必须将差分数组除第一位以外的数都变成0

我们怎么用最小的次数将差分数组变成零呢?这个样例不算明显,我们再造一个:
input : 1 4 2 5
差分: 1 3 -2 3

差分数组出现了负数,我们知道差分处理区间加法是将left+1,right的下一位-1,显然每次操作需要将正数-1,负数+1,显然需要尽可能将正数和负数放在一个操作中处理。

那么到这里,第一问求最小次数就已经解决了,形式化来讲,若设差分数组为c,则答案为:
$ max(\sum_{i = 2}^{n}[c[i]>0] ,\sum_{i=2}^{n}abs([[c[i]<0]))$


再看第二问

在保证最少次数的前提下,最终得到的数列有多少种。

首先,因为要保证最小次数,我们必须要将正数和负数配对消的部分消完才行。

显然将正数负数配对部分消完后,差分数组除了第一位至多还剩下一个数不为零,我们把这个数定义为\(k\),则满足:
\(k=\left| \sum_{i = 2}^{n}[c[i]>0] -\sum_{i=2}^{n}abs([[c[i]<0]) \right|\)

我们发现改变差分数组第一个数(即数组第一个数)不影响结果,所以求可能性要从这里入手。

考虑如何消掉最后剩下的\(k\)
首先明确,消除\(k\)可以和自己消,也可以和\(a[i]\)消。类似于递归,每一次都可以选择和自己或者\(a[1]\)消,显然只有改变了\(a[1]\)才改变了答案,最后产生\(k+1\)种答案(+1是因为可以不和1消)

到这里,代码已经很明确了:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define N 10000010
#define int long long
using namespace std;
int n;
int h[N];
int c[N];
int sum1=0,sum2=0;
signed main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&h[i]);
        c[i] = h[i] - h[i-1];
    }
    for(int i=2;i<=n;i++)
    {
        if(c[i] > 0) sum1+=c[i];
        else sum2+=abs(c[i]);
    }
    cout<<max(sum1,sum2)<<endl<<abs(sum1-sum2)+1<<endl;
    return 0;
}