下个月就要去南京打ICPC了,今天和队友一起做一下这两年的南京ICPC题,这道题原本已经有正解的思路想法了,但是在实现过程没有想到快捷的方法,交cf上面出现了heap overflow的情况。赛后遗憾补题,在这里来总结一下这道题。
题意:
给定一个数组,和一个数字k,你可以选择对数组的一个子数组一致加上数字k,问选择操作或者不操作后能得到的最大众数是多少。
注意这里的数据范围,n是1e6,数组内的元素是-1e6到正1e6,又因为题目跟众数有关,所以很容易想到用一个桶将数字的数量存起来,这里就需要对输入的数字统一加上1e6,防止出现负数下标。
然后不难想到一个性质,最终答案的众数一定是原本就已经出现在数组中的元素,因为如果是通过加上k后得到的新的众数,那么这些新众数在加之前的数量也等于加之前的数量。所以我们只需要枚举数组中的元素作为最终的众数,然后来考虑如何使当前枚举到的数的数量更多。
对于一个数字num,选择加与不加,只会影响num和num+k,假设枚举到了arr[i],单独只看数组内的arr[i]和arr[i]-k,我们相当于要选中一些区间,将里面的arr[i]移除,arr[i]-k变成arr[i],并且使arr[i]的数量最大。
我是考虑相当于对于每个众数,开一个vector存+1,-1,如果数组里出现arr[i]-k,就插一个+1,出现一个arr[i],就插入一个-1,选择区间统一加k就相当于选择一个区间,新出现的arr[i]数量相当于这个区间和,所以我们要选择一个区间和最大的子区间,将原来有的数量加上这个区间和,就是新产生的总数量,这个的最大值就是答案。
问题在于如何快速的找这个最大子区间。我自己做题时,找子区间我想去用双指针在那走,然后走来走去走乱了,不过我也有发现一个关键点,就是如果区间和是负数了,那直接清零,相当于不要了。所以可以直接扫一遍过去,用一个变量存一下区间和,如果负了直接清零,正的话就维护一下最大值。
而我补题时看的题解里面就更巧妙了,这个代码相当于同时处理多个数字的最大区间和,开一个数组cnt[maxn]维护当前枚举到的数的区间和,然后每更新一次,就判断一次答案是否用这个区间和来更新更大。就可以在扫一遍数组的同时完成所有数的维护。
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
//#define int long long
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pll pair<long long,long long>
using namespace std;
const int maxn=4e6+50;
int arr[maxn],num[maxn],dp[maxn];
int read(){
int x=0,f=0,ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return f?-x:x;
}
signed main(){
int n=read(),k=read();
int ans=0;
for(int i=1;i<=n;i++) arr[i]=read()+2e6,num[arr[i]]++;
for(int i=1;i<=n;i++){
dp[arr[i]]--;
dp[arr[i]+k]++;
if(dp[arr[i]]<0) dp[arr[i]]=0;
ans=max(ans,num[arr[i]+k]+dp[arr[i]+k]);
}
cout<<ans<<endl;
system("pause");
return 0;
}