汕头市队赛 C SRM 05 - YYL 杯 R1 T3!
C SRM 05 - YYL 杯 R1
背景
tjmak
描述
给一个大小为n的序列V。序列里的元素有正有负。问至少要删除多少个元素使得序列里不存在区间(要求非空)和 >= S.如果答案大于m,则输出-1
输入格式
第一行3个整数n,m,S含义如上
第二行n个整数 vi
输出格式
一个整数,表示至少要删除多少个数字。
样例输入
9 1000001 11 4 5 6 -8 5 6 6 4 3
样例输出
4
数据范围与约定
样例解释
删除第3,第5,第6和第7个数字。
来源
原创
这道题我确实是想了很久 首先我们的图应该长这样
那么我们发现以一个点为右端点的话 只要他所在的区间最大值合法 那么当前其他的区间也一定合法
而我们一个区间的值就是当前点的前缀和减去前缀和的最小值(一定<=0) 那么这一定是最大的区间了(其实最后和前缀最小没有任何关系)
那么我们堆中维护的是:在 当前位置 和 前缀和最小值所在位置 之间 删去一个数 能使 (当前前缀和-前缀和最小值) 减少多少
//其实我最后是平衡树实现的(用的multiset)
我们插入一数分两类
1.新加一个正数,直接入堆如果因此 当前前缀和-前缀和最小值 >=S,就删去一个数,同时pop(一个就好了)
2.新加一个负数x,不断pop出堆中最小值加到x上,直至x>=0,此时把x入堆,这代表最后一次加到x上的那个操作,因为这个负数而代价变为x,同时有其它被pop的操作变得没有意义
这个得解释一下 比如:
比如堆中是2 3 4 5,当前加入-7,2+3+4-7=2≥0,堆中变为2 5
因为-7的效果 操作了5之后,再操作一次前缀和最小处就会移到-7后面,前面的操作就失效了 删除4的效果被减弱至2
至于为什么是2呢 是这样的 删数的时候我们贪心删的第一个肯定是5对吧
那我们再删四的时候 2+3-7=-2<0 这个时候我们的区间最大值应该从0重新开始算 所以四的效果只有2
“这个等价变换我觉得非常妙”(by ccz)
这样处理过后其实就很好写了
当然 我们堆中元素也相当于数列已处理部分的一个等价
插入正数然后的pop相当于删去一个数(这个时候答案要+1)
插入负数然后的pop相当于证明了某些位置不可能被删除
而也容易证明前缀最小值的左边的数都是完全没有用的所以完全不用考虑 这也就是为什么sum'<0的时候直接clear就好了
因为前面的都是没用的了 我们计数肯定从0开始
删去对后面的最值没有任何影响 同时我们删的都是正数,区间只会从不合法变成合法
最后为了方便我用的multiset实现 其实和平衡树差不多
当然也可以用堆实现但是要四个 我懒啊 所以就调了STL
23333
其他的看代码吧
#include<cstdio> #include<cstring> #include<algorithm> #include<set> using namespace std; const int mod=1e9+7; int read(){ int ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+(c-'0'); c=getchar();} return ans*f; } int n,m,sum,ans,S,x; multiset<int> q; multiset<int>::iterator now; void water(){ for(int i=1;i<=n;i++){ x=read(); if(x>=S) ans++; if(ans>m) break; } printf("%d\n",ans>m?-1:ans); } int main() { n=read(); m=read(); S=read(); if(S<=0){water(); return 0;} for(int i=1;i<=n;i++){ x=read(); if(sum+x<=0) q.clear(),sum=0; else if(x<0){ sum+=x; while(x<0){ now=q.begin(); q.erase(now); x+=*now; if(x>0) q.insert(x); } } else{ sum+=x; q.insert(x); if(sum<S) continue; ans++; now=q.end(); now--; q.erase(now); sum-=*now; } } printf("%d\n",ans>m?-1:ans); return 0; }