题目描述
小熊的水果店里摆放着一排 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