首页 > 代码库 > [HNOI2002]营业额统计
[HNOI2002]营业额统计
传送门
题目大意:
求一段序列,小于当前元素的最大值和大于当前元素的最小值。
从该元素前面的元素找。
题解:
建立线段树维护或者使用双向链表...或stl水过
线段树每次插入一个新值,查询大于它的最小值和小于它的最大值
双向链表有点神...我们知道排序后一个数的前驱就是小于它的最大值
后继就是大于它的最小值,我们将原序列排序,如样例
5 1 2 5 4 6
排序后..
1 2 4 5 5 6
然后每次查找原序列最后一个数在排序后的位置,并将查找后位置数与它前驱和后继差的最小值加入答案,
并在排序后序列和原序列删掉这个数,重复上述步骤。
模拟一下...
找到原序列的自后一个数6,对答案的贡献是6-5=1,因为没有后继所以只能和前驱相减啦。
然后排序后序列变成..
1 2 4 5 5 6
再找最后一个4,对答案贡献是min(5-4,4-2)是1,
然后变成
1 2 4 5 5 6
重复就可以了
为什么要倒着做并删除呢...
因为如果你正着来...第一个是5对答案的贡献是min(5-4,5-5)是0,可是5是第一天的营业额啊,你怎么能和后面的减呢
反之...倒着来贡献完答案并在序列中删掉就是下一天的营业额所面临的情况了...
蒟蒻扯多了...orz
线段树代码:
#include<iostream> #include<cstdio> #include<algorithm> #define N 50000 #define inf 0x7fffffff using namespace std; int n,ord,ret,ans; int a[N],d[N]; struct Date{ int x,ord; }date[N]; struct T{ int l,r,sum,mx,mn; }t[N<<2]; bool cmp(Date a,Date b){ return a.x<b.x; } void update(int rt){ t[rt].sum=t[rt<<1].sum+t[rt<<1|1].sum; t[rt].mx=max(t[rt<<1].mx,t[rt<<1|1].mx); t[rt].mn=min(t[rt<<1].mn,t[rt<<1|1].mn); } void build(int rt,int l,int r){ t[rt].l=l;t[rt].r=r; t[rt].mn=inf-1;t[rt].mx=-1; if(l==r)return; int mid=(l+r)>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); // if(l<=mid) build(rt<<1,l,mid); // if(r>mid) build(rt<<1|1,mid+1,r); } void modify(int rt,int pos){ if(t[rt].l==t[rt].r){ t[rt].sum=1; t[rt].mx=t[rt].mn=t[rt].l; return; } int mid=(t[rt].l+t[rt].r)>>1; if(pos<=mid)modify(rt<<1,pos); if(pos>mid)modify(rt<<1|1,pos); update(rt); } int querymn(int rt,int l,int r){ if(r<l)return inf-1; if(t[rt].sum==0)return inf-1; if(l<=t[rt].l&&t[rt].r<=r)return t[rt].mn; int ret=inf,mid=(t[rt].l+t[rt].r)>>1; if(l<=mid) // ret=min(ret,querymn(rt<<1,l,mid)); ret=min(ret,querymn(rt<<1,l,r)); if(r>mid) // ret=min(ret,querymn(rt<<1|1,mid+1,r)); ret=min(ret,querymn(rt<<1|1,l,r)); return ret; } int querymx(int rt,int l,int r){ if(r<l) return -1; if(t[rt].sum==0) return -1; if(l<=t[rt].l&&t[rt].r<=r) return t[rt].mx; int mid=(t[rt].l+t[rt].r)>>1,ret=-2; if(l<=mid) ret=max(ret,querymx(rt<<1,l,r)); if(r>mid) ret=max(ret,querymx(rt<<1|1,l,r)); return ret; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&date[i].x); date[i].ord=i; } sort(date+1,date+n+1,cmp); a[date[1].ord]=1;d[1]=date[1].x;ord=1; for(int i=2;i<=n;i++){ if(date[i].x!=date[i-1].x)ord++; a[date[i].ord]=ord;d[ord]=date[i].x; } build(1,1,ord); ans=d[a[1]];modify(1,a[1]); for(int i=2;i<=n;i++){ int x=querymx(1,1,a[i]-1),y=querymn(1,a[i],n); ret=inf; if(x!=-1)ret=min(ret,d[a[i]]-d[x]); if(y!=inf-1)ret=min(ret,d[y]-d[a[i]]); ans+=ret; modify(1,a[i]); } printf("%d\n",ans); return 0; }
双向链表代码
#include<iostream> #include<algorithm> #include<cstdio> #define N 40000 #define inf 0x7fffffff using namespace std; int n,ans,t,tmp,pm[N],pre[N],nex[N]; struct Data{ int x,id; bool operator < (const Data &a) const { return x<a.x; } }data[N]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&data[i].x); data[i].id=i; pre[i]=(i==1)?0:i-1;nex[i]=(i==n)?0:i+1; } ans=data[1].x; sort(data+1,data+n+1); for(int i=1;i<=n;i++)pm[data[i].id]=i;//pm[i]=j在原序列排名为i的是第J大 for(int i=n;i>=1;i--){ tmp=inf; t=pm[i];//**** if(pre[t]!=0)tmp=min(tmp,abs(data[t].x-data[pre[t]].x)); if(nex[t]!=0)tmp=min(tmp,abs(data[nex[t]].x-data[t].x)); if(tmp!=inf){ ans+=tmp; nex[pre[t]]=nex[t]; pre[nex[t]]=pre[t]; } } printf("%d\n",ans); return 0; }
然后线段树
双向链表
hehe...
其实一开始双向链表是120ms,然后我就把sort的cmp改成了operator快了19ms,101ms,
然后我又把代码中的pm[i]赋值给了t,这样就不会出现括号套括号重复打的情况了...结果...woc///
109ms-->54ms质的飞跃成为人生赢家...原来代码写得丑和W T也是有关系的....
最后
rank3人生赢家...QWQ..
stl set代码
#include<iostream> #include<cstdio> #include<set> #include<algorithm> using namespace std; set<int>s; int n,x,a,b,ans; int main(){ scanf("%d%d",&n,&x); s.insert(x);ans+=x; for(int i=1;i<n;i++){ scanf("%d",&x); set<int>::iterator a=s.lower_bound(x); set<int>::iterator b=a; if(a!=s.begin())a--; if(b==s.end())b--; ans+=min(abs(x-*a),abs(*b-x)); s.insert(x); } printf("%d\n",ans); return 0; }
真的是水过..
[HNOI2002]营业额统计