刷题笔记:P4452 [差分]

题目传送门: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\limits_{i = 2}^{n}[c[i]>0] ,\sum\limits_{i=2}^{n}abs([[c[i]<0]))$


再看第二问

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

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

显然将正数负数配对部分消完后,差分数组除了第一位至多还剩下一个数不为零,我们把这个数定义为\(k\),则满足:
\(k=\left| \sum\limits_{i = 2}^{n}[c[i]>0] -\sum\limits_{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;
}
posted @   SXqwq  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示