牛客模拟赛 下雨天 题解

算是我见过肥肠优美的一道题了呢qvq

链接:https://ac.nowcoder.com/acm/contest/1105/B
来源:牛客网

题目描述

小多有n个池塘,第i个池塘容量为i,一开始都没有水。

随后的很多天,第i天的天气状况决定了所有水池同时的水量变化bib_ibibi>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;
}

 

posted @ 2020-01-09 23:40  YoOXiii  阅读(267)  评论(0编辑  收藏  举报