牛客模拟赛 下雨天 题解
算是我见过肥肠优美的一道题了呢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;
}

浙公网安备 33010602011771号