P8148 声海 Sea of Voices
题面
题解
注意到ai是单调不降的,那么很显然a1和a2是可重集S中最小的两个。因此,a1和a2很容易得到,那么进一步考虑a3,发现在删去a1和a2后,a3可能是最小的。因为无法确定a3和a1+a2的大小关系,那么很自然可以想到,如果再将a1+a2删去,a3就为最小的了。
以此类推,再已知a1到ak时,将其能计算出的结果从S中删去,那么最小的就为ak+1了。按照这个思路,先将S排序,然后暴力维护能够删除的结果,能够得到50分。
考虑优化,发现这个过程有点像筛法。都是从一堆数中取出符合条件的数,并且都能通过已经得到的结果来对剩余的数进行筛选。我们要保留的数是ai要筛除的数是∑j=lraj,注意到ai≤105,因此我们只需要筛除∑j=lraj≤105,我们可以开一个桶来处理。
这个桶有以下特点:
- 当一个数出现时对应的桶+1
- 当找到一个ai时,将这个ai能生成的所有后缀和所对应的桶都−1
- 一个桶如果非负,那么这个桶对应的数为ai
这个桶就相当于相当于一个标记数组,我们通过操作标记数组来对原数据进行筛除。需要注意的是这里在筛除数据时使用的是后缀和而非前缀和,举个例子:
a3筛除的是:
a3a2+a3a1+a2+a3
a4筛除的是:
a4a3+a4a2+a3+a4a1+a2+a3+a4
很明显,后缀和能够不重复不遗漏的将需要筛除的数计算出来
#include<iostream>
#include<algorithm>
#include<cstdio>
#define N 2005
#define M 4000005
using namespace std;
int n,m,s[M];
int a[N],cnt;
int t[100005];
int main(){
scanf("%d",&n);
m=n*(n+1)/2;
for(int i=1;i<=m;i++) scanf("%d",&s[i]);
sort(s+1,s+m+1);
for(int i=1;i<=m;i++){
t[s[i]]++;
if(t[s[i]]>0){
a[++cnt]=s[i];
if(cnt==n) break;
int sum=0;
for(int i=cnt;i>=1;i--){
sum+=a[i];
if(sum>100000) break;
t[sum]--;
}
}
}
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
puts("");
return 0;
}