csp23前二题,数组推导,非零段划分

数组推导

A1,A2,⋯,AnA1,A2,⋯,An 是一个由 nn 个自然数(即非负整数)组成的数组。

在此基础上,我们用数组 B1⋯BnB1⋯Bn 表示 AA 的前缀最大值。

Bi=max{A1,A2,⋯,Ai}Bi=max{A1,A2,⋯,Ai}

如上所示,BiBi 定义为数组 AA 中前 ii 个数的最大值。

根据该定义易知 A1=B1A1=B1,且随着 ii 的增大,BiBi 单调不降。

此外,我们用 sum=A1+A2+⋯+Ansum=A1+A2+⋯+An 表示数组 AA 中 nn 个数的总和。

现已知数组 BB,我们想要根据 BB 的值来反推数组 AA。

显然,对于给定的 BB,AA 的取值可能并不唯一。

试计算,在数组 AA 所有可能的取值情况中,sumsum 的最大值和最小值分别是多少?

输入格式

输入的第一行包含一个正整数 nn。

输入的第二行包含 nn 个用空格分隔的自然数 B1,B2,⋯,BnB1,B2,⋯,Bn。

输出格式

输出共两行。

第一行输出一个整数,表示 sumsum 的最大值。

第二行输出一个整数,表示 sumsum 的最小值。

数据范围

50%50% 的测试数据满足数组 BB 单调递增,即 0<B1<B2<⋯<Bn<1050<B1<B2<⋯<Bn<105;
全部的测试数据满足 n≤100n≤100 且数组 BB 单调不降,即 0≤B1≤B2≤⋯≤Bn≤1050≤B1≤B2≤⋯≤Bn≤105。

输入样例1:
6
0 0 5 5 10 10
输出样例1:
30
15
样例1解释

数组 AA 的可能取值包括但不限于以下三种情况。

  • 情况一:A=[0,0,5,5,10,10]A=[0,0,5,5,10,10]
  • 情况二:A=[0,0,5,3,10,4]A=[0,0,5,3,10,4]
  • 情况三:A=[0,0,5,0,10,0]A=[0,0,5,0,10,0]

其中第一种情况 sum=30sum=30 为最大值,第三种情况 sum=15sum=15 为最小值。

输入样例2:
7
10 20 30 40 50 60 75
输出样例2:
285
285
样例2解释

A=[10,20,30,40,50,60,75]A=[10,20,30,40,50,60,75] 是唯一可能的取值,所以 sumsum 的最大、最小值均为 285285。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
int b[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> b[i];

    int maxs = 0, mins = 0;
    for (int i = 0; i < n; i ++ )
    {
        maxs += b[i];
        if (!i || b[i] > b[i - 1])
            mins += b[i];
    }

    cout << maxs << endl;
    cout << mins << endl;

    return 0;
}

非零段划分

A1,A2,⋯,AnA1,A2,⋯,An 是一个由 nn 个自然数(非负整数)组成的数组。

我们称其中 Ai,⋯,AjAi,⋯,Aj 是一个非零段,当且仅当以下条件同时满足:

  • 1≤i≤j≤n1≤i≤j≤n;
  • 对于任意的整数 kk,若 i≤k≤ji≤k≤j,则 Ak>0Ak>0;
  • i=1i=1 或 Ai−1=0Ai−1=0;
  • j=nj=n 或 Aj+1=0Aj+1=0。

下面展示了几个简单的例子:

  • A=[3,1,2,0,0,2,0,4,5,0,2]A=[3,1,2,0,0,2,0,4,5,0,2] 中的 44 个非零段依次为 [3,1,2][3,1,2]、[2][2]、[4,5][4,5] 和 [2][2];
  • A=[2,3,1,4,5]A=[2,3,1,4,5] 仅有 11 个非零段;
  • A=[0,0,0]A=[0,0,0] 则不含非零段(即非零段个数为 00)。

现在我们可以对数组 AA 进行如下操作:任选一个正整数 pp,然后将 AA 中所有小于 pp 的数都变为 00。

试选取一个合适的 pp,使得数组 AA 中的非零段个数达到最大。

若输入的 AA 所含非零段数已达最大值,可取 p=1p=1,即不对 AA 做任何修改。

输入格式

输入的第一行包含一个正整数 nn。

输入的第二行包含 nn 个用空格分隔的自然数 A1,A2,⋯,AnA1,A2,⋯,An。

输出格式

仅输出一个整数,表示对数组 AA 进行操作后,其非零段个数能达到的最大值。

数据范围

70%70% 的测试数据满足 n≤1000n≤1000;
全部的测试数据满足 n≤5×105n≤5×105,且数组 AA 中的每一个数均不超过 104104。

输入样例1:
11
3 1 2 0 0 2 0 4 5 0 2
输出样例1:
5
样例1解释

p=2p=2 时,A=[3,0,2,0,0,2,0,4,5,0,2]A=[3,0,2,0,0,2,0,4,5,0,2],55 个非零段依次为 [3][3]、[2][2]、[2][2]、[4,5][4,5] 和 [2][2];此时非零段个数达到最大。

输入样例2:
14
5 1 20 10 10 10 10 15 10 20 1 5 10 15
输出样例2:
4
样例2解释

p=12p=12 时,A=[0,0,20,0,0,0,0,15,0,20,0,0,0,15]A=[0,0,20,0,0,0,0,15,0,20,0,0,0,15],44 个非零段依次为 [20][20]、[15][15]、[20][20] 和 [15][15];此时非零段个数达到最大。

输入样例3:
3
1 0 0
输出样例3:
1
样例3解释

p=1p=1 时,A=[1,0,0]A=[1,0,0],此时仅有 11 个非零段 [1][1],非零段个数达到最大。

输入样例4:
3
0 0 0
输出样例4:
0
样例4解释

无论 pp 取何值,AA 都不含有非零段,故非零段个数至多为 00。

70分

#include <iostream>
using namespace std;
int num[500000];

int main()
{
    int n, p, phrase = 0; // 整数个数,处理数p,段数
    int i, min = 10000, max = 0;

    cin >> n;
    for(i = 0; i < n; i++)
    {
        cin >> num[i];
        if(num[i] < min)
            min = num[i];
        if(num[i] > max)
            max = num[i];
    }

    if(min == max)
    {
        cout << 0;
        return 0;
    }

    int temp;
    bool flag;
    // min = 1 > min ? 1 : min;
    for(p = min; p < max; p++)
    {
        temp = 0;
        flag = true;
        for(i = 0; i < n; i++)
        {
            if(num[i] < p)
            {
                flag = true;
            }
            else
            {
                if(flag)
                {
                    temp += 1;
                    flag = false;
                }
            }
        }
        if (temp > phrase)
            phrase = temp;
    }

    cout << phrase;
    return 0;
}

算法2(100分)
借助岛屿问题
时间复杂度 O(n)
𝑂
(
𝑛
)

考虑p足够大的情况,这时所有的岛都被海水淹没了,只有0个岛屿
然后海平面逐渐下降,岛屿数量开始变化
每当一个凸峰出现,岛屿数就会多一个;
每当一个凹谷出现,原本相邻的两个岛屿就被这个凹谷连在一起了,岛屿数减少一个
可以使用数组cnt[],cnt[i] 表示海平面 下降 到i时,岛屿数量的变化

这样,数组元素cnt[i]中存储的就是该元素被替换为0时,划分数变化的差分值
最大值则只需要从其前缀和(程序中实际为 后缀和)中找出最大值

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 500010, M = 10010;

int n;
int a[N], cnt[M];

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    n = unique(a + 1, a + n + 1) - a - 1;
    a[0] = a[n + 1] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int x = a[i - 1], y = a[i], z = a[i + 1];
        if (x < y && z < y) cnt[y] ++ ;
        else if (x > y && z > y) cnt[y] -- ;
    }

    int res = 0, sum = 0;
    for (int i = M - 1; i; i -- )
    {
        sum += cnt[i];
        res = max(res, sum);
    }

    printf("%d\n", res);
    return 0;
}
#include <iostream>
using namespace std;

const int N = 500004;
const int M = 10004;
int a[N], cnt[N];

int main()
{
    int n, phrase = 0; // 整数个数,段数
    int i;

    cin >> n;
    for(i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        if(a[i] > a[i-1])
        {
            cnt[a[i-1]+1]++;
            cnt[a[i]+1]--;
        }
    }

    int sum = 0;
    for(i = 1; i < M; i++)
    {
        sum += cnt[i];
        phrase = max(phrase, sum);
    }

    printf("%d\n", phrase);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值