P7912 [CSP-J 2021] 小熊的果篮

题目描述

小熊的水果店里摆放着一排 nn 个水果。每个水果只可能是苹果或桔子,从左到右依次用正整数 1, 2, \ldots, n1,2,…,n 编号。连续排在一起的同一种水果称为一个“块”。小熊要把这一排水果挑到若干个果篮里,具体方法是:每次都把每一个“块”中最左边的水果同时挑出,组成一个果篮。重复这一操作,直至水果用完。注意,每次挑完一个果篮后,“块”可能会发生变化。比如两个苹果“块”之间的唯一桔子被挑走后,两个苹果“块”就变成了一个“块”。请帮小熊计算每个果篮里包含的水果。

输入格式

第一行,包含一个正整数 nn,表示水果的数量。

第二行,包含 nn 个空格分隔的整数,其中第 ii 个数表示编号为 ii 的水果的种类,11 代表苹果,00 代表桔子。

输出格式

输出若干行。

第 ii 行表示第 ii 次挑出的水果组成的果篮。从小到大排序输出该果篮中所有水果的编号,每两个

样例

输入输出样例

输入 #1

12
1 1 0 0 1 1 1 0 1 1 0 0

输出 #1

1 3 5 8 9 11
2 4 6 12
7
10

输入 #2

20
1 1 1 1 0 0 0 1 1 1 0 0 1 0 1 1 0 0 0 0

输出 #2

1 5 8 11 13 14 15 17
2 6 9 12 16 18
3 7 10 19
4 20

说明/提示

【样例解释 #1】

这是第一组数据的样例说明。

所有水果一开始的情况是 [1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0][1,1,0,0,1,1,1,0,1,1,0,0],一共有 66 个块。

在第一次挑水果组成果篮的过程中,编号为 1, 3, 5, 8, 9, 111,3,5,8,9,11 的水果被挑了出来。

之后剩下的水果是 [1, 0, 1, 1, 1, 0][1,0,1,1,1,0],一共 44 个块。

在第二次挑水果组成果篮的过程中,编号为 2, 4, 6, 122,4,6,12 的水果被挑了出来。

之后剩下的水果是 [1, 1][1,1],只有 11 个块。

在第三次挑水果组成果篮的过程中,编号为 77 的水果被挑了出来。

最后剩下的水果是 [1][1],只有 11 个块。

在第四次挑水果组成果篮的过程中,编号为 1010 的水果被挑了出来。

【数据范围】

对于 10 \%10% 的数据,n \le 5n≤5。
对于 30 \%30% 的数据,n \le 1000n≤1000。
对于 70 \%70% 的数据,n \le 50000n≤50000。
对于 100 \%100% 的数据,1 \le n \le 2 \times {10}^51≤n≤2×105。

【提示】

由于数据规模较大,建议 C/C++ 选手使用 scanf 和 printf 语句输入、输出。

 (原题出自于洛谷)

非最优解

通过遍历

只要这个水果与上一类不同,则说明这个水果是每一个“块”中最左边的水果;

比如

 9

 1 1 0 0 1 1 1 0 1 

 1 2 3 4 5 6 7 8 9 (红色代表编号)

设置边界

-1 1 1 0 0 1 1 1 0 1 -1

 0 1 2 3 4 5 6 7 8 9 10

首先看第一个水果

-1 1 1 0 0 1 1 1 0 1 -1

 0 1 2 3 4 5 6 7 8 9 10

因为1!=-1也就是说明编号为1的水果与上一个水果不同

所以是这个水果是每一个“块”中最左边的水果

把它的状态设置为-1,也就是被拿走了

再看第二个水果

-1 -1 1 0 0 1 1 1 0 1 -1

 0 1 2 3 4 5 6 7 8 9 10

因为1==1也就是说明编号为2的水果与上一个水果相同

所以是这个水果不是这一个“块”中最左边的水果

然而

时间限制为1*10^8

假设是最坏的打算

有200000个水果

并且还是同一种

就有了

200000

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ......此处省略不知道多少个1

时间复杂度为n+n-1+n-2+n-3+,,,,,+1=(n-1)*n/2

大概是n^2

这不一定超时吗?

于是就有了非最优解代码

代码

//P7912 [CSP-J 2021] 小熊的果篮
#include<bits/stdc++.h>
using namespace std;
int a[200005],i,cnt=0,n,flag=2;
int main()
{
    cin>>n;//输入数量
    for(i=1;i<=n;i++)scanf("%d",&a[i]);//循环输入种类,因为数据量大,所以使用scanf节约时间
    while(cnt<n)//只要挑出水果的数量大于或等于n,则说明水果挑完了
    {
        flag=2;//水果种类初始化
        for(i=1;i<=n;i++)//循环
        {
            if(a[i]!=flag&&a[i]!=-1)//判断水果没被挑出,并且与上一类不同
            {
                printf("%d ",i);//说明水果是在这个块中最左边的一个
                flag=a[i];//保存种类
                a[i]=-1;//更改状态
                cnt++;//统计数量加1
            }
        }
        printf("\n");//换行
    }
}

 实测得分在75-60分之间(可能不一样)

最优解法

模拟链表:

其实用上一个模拟挑选过程的方法会超时很正常

因为当这个水果被挑选后,虽然他的状态会变成-1,但是在经历循环遍历的时候,还是会遍历到它

这个时候,我们就需要用到链表中的假删除

首先看看假删除的过程

创建数组ans(存果篮里的水果),l(水果左边的水果编号),r(水果右边的水果编号),a(水果编号)

首先找到第一篮水果

ans 1 3 5 8 9 11

先看第一篮的第一个水果

ans 1 3 5 8 9 11(编号)

a -1  1  1  0 0 1 1 1 0 1  1   0   0   -1(记得边界)

    0  1  2  3 4 5 6 7 8 9 10 11 12 13(编号)

假删除

z=l[ans[i]];//保存左边的编号
y=r[ans[i]];//保存右边的编号
l[y]=z;//它右边水果的左边为它左边水果
r[z]=y;//它左边水果的右边为它右边水果

每当遍历时,都会找到下一篮的水果编号

代码如下

#include<bits/stdc++.h>
using namespace std;
int ans[200005],l[200005],r[200005],a[200005],z,y;//定义变量
int main()
{
    int i,n,len=0,len1;//定义变量
    cin>>n;//输入水果数量
    a[0]=a[n+1]=-1;//设置边界
    for(i=1;i<=n;i++)//循环输入水果种类
    {
        scanf("%d",&a[i]);//输入水果种类,使用scanf省时间
        l[i]=i-1,r[i]=i+1;//记录水果左右两边的编号
        if(a[i]!=a[i-1])//如果第i个水果与它上一个水果不同
        {
            ans[++len]=i;//则说明它是最左边的水果,记录下来
        }
    }
    
    while(len)//只要还可以凑出果篮,就一直循环
    {
        len1=0;//初始化
        for(i=1;i<=len;i++)//遍历ans
        {
            printf("%d ",ans[i]);//输出ans里的值
            z=l[ans[i]];//保存左边的编号
            y=r[ans[i]];//保存右边的编号
            l[y]=z;//它右边水果的左边为它左边水果
            r[z]=y;//它左边水果的右边为它右边水果
            if(a[ans[i]]==a[y]&&a[z]!=a[y])ans[++len1]=y;//判断下一个水果是否是最左边的水果,是则覆盖原来的值
        }
        len=len1;//把下一次果篮的果子数量保存在len
        cout<<endl;//换行
    }
}

 最后,喜提AC

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值