原题题意
给出长度为n的有序数组,m次询问,每次给出一个正整数x。你要删除数组中最少的元素,使得数组中的前缀和+x都为非负整数。允许离线,n≤750,m≤200,000。
原题思路
首先注意到,x能成功通过测试当且仅当前缀和中最小的数≥x。
将询问从大到小排个序,对于一个新的询问,每次尝试从数组中删除最优的一个数,使得成功的机会更大。
何为最优?我们注意到,ai只会对后面的数造成影响。设当前前缀和最小为now,fi为前i个前缀和中最小的数,则答案会增加 max { min { now-ai , fi-1 } } (请注意后面的f囊括了now-ai顾及不到的情况)。
每次判断最小的数在哪,暴力更新,O(n3+mlogm)。
代码
1 #pragma GCC optimize(2) 2 #include<bits/stdc++.h> 3 using namespace std; 4 typedef long long int ll; 5 const ll inf=1000000000000000000; 6 const ll maxn=1E6+5; 7 template<typename T> void read(T &x){ 8 x=0;char ch=getchar();int fh=1; 9 while (ch<'0'||ch>'9'){if (ch=='-')fh=-1;ch=getchar();} 10 while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 11 x=x*fh; 12 } 13 void write(ll x) 14 { 15 if(x==0){putchar('0');putchar('\n');return;} 16 if(x<0){putchar('-');x=-x;} 17 ll a[25],size=0; 18 while(x){a[++size]=x%10;x/=10;} 19 for(int i=size;i>=1;--i)putchar(a[i]+'0'); 20 putchar('\n'); 21 } 22 ll min(ll x,ll y){return x<y?x:y;} 23 ll max(ll x,ll y){return x>y?x:y;} 24 ll n,m,a[maxn],tot,sum[maxn],ans[maxn],f[maxn]; 25 struct query{ll pos,x;}Q[maxn]; 26 bool cmp(query a,query b){return a.x>b.x;} 27 ll get() 28 { 29 ll ans=inf; 30 for(int i=1;i<=n;++i) 31 { 32 sum[i]=sum[i-1]+a[i]; 33 ans=min(ans,sum[i]); 34 f[i]=min(f[i-1],sum[i]); 35 } 36 return ans; 37 } 38 int main() 39 { 40 read(n);read(m); 41 for(int i=1;i<=n;++i) 42 { 43 read(a[i]); 44 if(a[i]<0)++tot; 45 } 46 for(int i=1;i<=m;++i) 47 { 48 read(Q[i].x); 49 Q[i].pos=i; 50 } 51 sort(Q+1,Q+m+1,cmp); 52 int pos=1; 53 for(int i=1;i<=tot;++i) 54 { 55 ll g=get(); 56 while(Q[pos].x+g>=0&&pos<=m){ans[Q[pos].pos]=i-1;++pos;} 57 if(g>=0)break; 58 ll ans=-inf,pos=0; 59 for(int j=1;j<=n;++j) 60 { 61 if(min(g-a[j],f[j-1])>ans) 62 { 63 ans=min(g-a[j],f[j-1]); 64 pos=j; 65 } 66 } 67 a[pos]=0; 68 } 69 ll g=get(); 70 while(Q[pos].x+g>=0&&pos<=m){ans[Q[pos].pos]=tot;++pos;} 71 for(int i=1;i<=m;++i)write(ans[i]); 72 return 0; 73 }
EX版本:
m,n≤1,000,000,强制在线。
思路
考虑从后往前贪心。我们先只考虑两种情况(0显然没必要考虑)。
第一种,所有的数均为负数。则每次答案必然会从最小的数删起,直到删的数的绝对值第一次大于等于询问。
第二种,只有一个数为正,其余均为负。两种情况:
第一种,加起来为仍为正,那么就不会删数。并且这一位不会对以后造成任何影响(既然都加上正数了,能到达这一位,以后肯定不会小于零)。
第二种,加起来为负。那么会贪心地选择最小的负数删,直到加起来为正。换个角度,会贪心地选择最大的负数加到正数上,直到正数为负。这样,化归为第一情况。
再将负数加入大根堆,正数不要的原因同第一种。
图画画,手算算至少能够理解。
最后得到的答案要么剩下正数,要么全是负数。这些负数取反后,从大到小排序的第i位代表了取到第i种答案,要删去1~i个数。
二分查找即可。
代码
1 #pragma GCC optimize(2) 2 #include<bits/stdc++.h> 3 using namespace std; 4 typedef long long int ll; 5 const ll inf=1000000000000000000; 6 const ll maxn=1E6+5; 7 ll min(ll x,ll y){return x<y?x:y;} 8 ll max(ll x,ll y){return x>y?x:y;} 9 template<typename T> void read(T &x){ 10 x=0;char ch=getchar();int fh=1; 11 while (ch<'0'||ch>'9'){if (ch=='-')fh=-1;ch=getchar();} 12 while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 13 x=x*fh; 14 } 15 void write(ll x) 16 { 17 if(x==0){putchar('0');putchar('\n');return;} 18 if(x<0){putchar('-');x=-x;} 19 ll a[25],size=0; 20 while(x){a[++size]=x%10;x/=10;} 21 for(int i=size;i>=1;--i)putchar(a[i]+'0'); 22 putchar('\n'); 23 } 24 ll n,m,a[maxn],wait[maxn],size,x; 25 priority_queue<ll>Q; 26 int main() 27 { 28 read(n);read(m); 29 for(int i=1;i<=n;++i)read(a[i]); 30 for(int i=n;i>=1;--i) 31 { 32 while(a[i]>=0&&!Q.empty()) 33 { 34 a[i]+=Q.top(); 35 Q.pop(); 36 } 37 if(a[i]<0)Q.push(a[i]); 38 } 39 while(!Q.empty()) 40 { 41 wait[++size]=Q.top(); 42 Q.pop(); 43 } 44 for(int i=1;i<=size/2;++i)swap(wait[i],wait[size-i+1]); 45 for(int i=1;i<=size;++i)wait[i]+=wait[i-1]; 46 for(int i=1;i<=size;++i)wait[i]=-wait[i]; 47 while(m--) 48 { 49 read(x); 50 write(lower_bound(wait,wait+size+1,wait[size]-x)-wait); 51 } 52 return 0; 53 }