牛客模拟赛 下雨天 题解
算是我见过肥肠优美的一道题了呢qvq
链接:https://ac.nowcoder.com/acm/contest/1105/B
来源:牛客网
题目描述
小多有n个池塘,第i个池塘容量为i,一开始都没有水。
随后的很多天,第i天的天气状况决定了所有水池同时的水量变化bib_ibi,bi>0b_i > 0bi>0表示在下雨,反之则表示在干旱。
如果某个池塘满了,天还在下雨,那么多余的水就会白白流失掉。同样的,如果池塘干了还在干旱,池塘也不会出现负水量。更正式地说,设第k个水池当前水量为u,经历了某天,水量如果增加v,那么该水池在这天过后的水量为min(u+v,k)\min( u+v, k )min(u+v,k);若v是个负数(水量减少),那么水量为max(0,u+v)\max( 0, u+v )max(0,u+v)。
随后的很多天,第i天的天气状况决定了所有水池同时的水量变化bib_ibi,bi>0b_i > 0bi>0表示在下雨,反之则表示在干旱。
如果某个池塘满了,天还在下雨,那么多余的水就会白白流失掉。同样的,如果池塘干了还在干旱,池塘也不会出现负水量。更正式地说,设第k个水池当前水量为u,经历了某天,水量如果增加v,那么该水池在这天过后的水量为min(u+v,k)\min( u+v, k )min(u+v,k);若v是个负数(水量减少),那么水量为max(0,u+v)\max( 0, u+v )max(0,u+v)。
对于随后的q天,小多希望知道每一天结束时,所有池塘的总水量。
暴力不解释
考虑基于n的算法,我们不难想到线段树之类的数据结构。发现每次修改都相当于有一个断点,两边一遍是区间填满/清空,另一边是区间改值,维护三个lazytag即可。中间的断点二分找到,如果利用类似线段树K大的方法可以做到qlogn,大概能跑个7/80 (但是我不会写于是写了qlog^2n的二分
代码很长 但是没啥细节
#include <cstdio> #include <iostream> #define int long long int #define rit register int using namespace std; inline int read() { int x=0,f=1; char cr=getchar(); while (cr>'9' || cr<'0') { if (cr=='-') f=-1; cr=getchar(); } while (cr>='0' && cr<='9') { x=(x<<3)+(x<<1)+cr-'0'; cr=getchar(); } return x*f; } const int maxn=1000050; bool Full[maxn<<2],Empty[maxn<<2]; int tr[maxn<<2],tag[maxn<<2]; inline int full_sum(int l,int r) { int x=l+r,y=r-l+1; if (x&1) return y/2*x; return x/2*y; } inline void add(int now,int l,int r,int w) { tr[now]+=(r-l+1)*w; tag[now]+=w; } inline void ful(int now,int l,int r) { tr[now]=full_sum(l,r),Empty[now]=0,tag[now]=0; Full[now]=1; } inline void emp(int now,int l,int r) { tr[now]=0,Full[now]=0,tag[now]=0; Empty[now]=1; } inline void pushdown(int now,int l,int r) { int mid=l+r>>1; if (Empty[now]) { emp(now<<1,l,mid); emp(now<<1|1,mid+1,r); Empty[now]=0; } if (Full[now]) { ful(now<<1,l,mid); ful(now<<1|1,mid+1,r); Full[now]=0; } if (tag[now]) { add(now<<1,l,mid,tag[now]); add(now<<1|1,mid+1,r,tag[now]); tag[now]=0; } } inline void paint(bool flag,int now,int l,int r,int x,int y) { if (x>y) return; if (x<=l && r<=y) { if (flag==0) return ful(now,l,r); if (flag==1) return emp(now,l,r); } int mid=l+r>>1; pushdown(now,l,r); if (x<=mid) paint(flag,now<<1,l,mid,x,y); if (mid+1<=y) paint(flag,now<<1|1,mid+1,r,x,y); tr[now]=tr[now<<1]+tr[now<<1|1]; } inline void modify(int now,int l,int r,int x,int y,int w) { if (x>y) return; if (x<=l && r<=y) return add(now,l,r,w); int mid=l+r>>1; pushdown(now,l,r); if (x<=mid) modify(now<<1,l,mid,x,y,w); if (mid+1<=y) modify(now<<1|1,mid+1,r,x,y,w); tr[now]=tr[now<<1]+tr[now<<1|1]; } inline int query(int now,int l,int r,int loc) { if (l==r && l==loc) return tr[now]; int mid=l+r>>1; pushdown(now,l,r); if (loc<=mid) return query(now<<1,l,mid,loc); if (mid+1<=loc) return query(now<<1|1,mid+1,r,loc); } int w[maxn]; inline void pianfen(int n,int q) { bool Full1=0,Empty1=0; for (int i=1;i<=q;i++) { int temp=read(); if (temp>=n) { printf("%lld\n",n*(n+1)/2); Full1=1; } if (temp<=-n) { printf("0\n"); Empty1=1; } else { if (Full1 && temp>=0) { printf("%lld\n",n*(n+1)/2); continue; } if (Empty1 && temp<=0) { printf("0\n"); continue; } else { Full1=Empty1=0; int ans=0; for (int j=1;j<=n;j++) { w[j]+=temp; if (w[j]>j) w[j]=j; if (w[j]<0) w[j]=0; ans+=w[j]; } printf("%lld\n",ans); } } } } signed main() { int n=read(),q=read(); //if (n>1000000) { // pianfen(n,q); // return 0; //} int lgn=0,tem=n; while (tem) tem>>=1,lgn++; //if (q * lgn * lgn>=1e8) { // pianfen(n,q); // return 0; //} int last=0; for (rit i=1;i<=q;i++) { int temp=read(); if (temp==0) { printf("%lld\n",last); continue; } if (temp>0) { int l=1,r=n; int loc=0; while (l<=r) { int mid=l+r>>1; if (mid-query(1,1,n,mid)<=temp) l=mid+1,loc=mid; else r=mid-1; } paint(0,1,1,n,1,loc); modify(1,1,n,loc+1,n,temp); } if (temp<0) { int l=1,r=n; int loc=n; while (l<=r) { int mid=l+r>>1; if (query(1,1,n,mid)>=-temp) r=mid-1,loc=mid; else l=mid+1; } paint(1,1,1,n,1,loc); modify(1,1,n,loc+1,n,temp); } last=tr[1]; printf("%lld\n",tr[1]); } return 0; }
下面我们考虑正解,由于每次都是一个断点,我们可以发现每次都会至多产生一个新的轮廓线段。
观察到q=1e6 我们有没有办法直接维护这些轮廓呢?
又观察到无论是v>0还是<0,首先受到影响的一定是左下方的轮廓 也就是最下面的一层梯形。而新增轮廓也是在最下面。我们想到了什么?
没错 用个栈膜你一下就行了 核心代码10行Orz
#include <cstdio> #include <algorithm> using namespace std; inline int read() { int x=0,f=1; char cr=getchar(); while (cr>'9' || cr<'0') { if (cr=='-') f=-1; cr=getchar(); } while (cr>='0' && cr<='9') { x=(x<<3)+(x<<1)+cr-'0'; cr=getchar(); } return x*f; } const int maxn=1000050; int x[maxn],h[maxn],top; int n,q,ans; inline int S(int x,int h) { return (n-x)*h + h*(h+1)/2; } int main() { n=read(),q=read(); for (int i=1;i<=q;i++) { int v=read(); if (v>0) { while (top && h[top]+v>=x[top]) ans-=S(x[top],h[top]),v+=h[top--]; h[++top]=min(n,v),x[top]=h[top],ans+=S(x[top],h[top]); } else { while (top && h[top]+v<=0) ans-=S(x[top],h[top]),v+=h[top--]; if (top) ans-=S(x[top],h[top]),h[top]+=v,ans+=S(x[top],h[top]); } printf("%lld\n",ans); } return 0; }