在51nod上膜拜了吉司机线段树的直播,写了两个题,分别是hdu5306以及bzoj4355,由于代码能力过于垃圾,每个题都写了两天。
主要思路是用cut对线段树的更新做剪枝,用check控制暴力更新的条件。大概就是区间覆盖或者类似的操作会把区间变得越来越相同,对于把区间变得相同(势能降低?)的操作,完全可以暴力更新,而没有降低势能的操作基本上可以打标记。
整个框架如下:
void update(int u, int ql, int qr, int c, int l, int r){
if(r<ql||qr<l||cut())return;
if(ql<=l&&r<=qr&&check()){
putlazy(u,c);
return;
}
int mid=(l+r)/2;
pushdown(u);
update(2*u, ql, qr, c, l, mid);
update(2*u+1, ql, qr, c, mid+1, r);
pushup(u);
}
对于hdu5306这个题,考虑区间与t取min这个操作,有如下几种情况:
case 1:t大于最大值,此时区间不变;
case 2:t小于严格次大值,此时至少把最大值和次大值变得相同,即使得区间变得相同,允许暴力更新;
case 3:t大于严格次大值,小于最大值,这里可以打懒标记。
考虑查询,只需维护最大值,最大值个数,严格次大值即可。
//吉司机宇宙线段树之王!
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000005;
typedef long long ll;
int mx[maxn<<2];
int cnt[maxn<<2];
int se[maxn<<2];
int lazy[maxn<<2];
ll sum[maxn<<2];
int a[maxn];
int n, m;
void putlazy(int u, int t){
sum[u]-=1LL*cnt[u]*(mx[u]-t);
mx[u]=t;
lazy[u]=t;
}
void pushdown(int u){
if(lazy[u]==-1)return;
if(mx[2*u]>lazy[u]){
sum[2*u]-=1LL*cnt[2*u]*(mx[2*u]-lazy[u]);
mx[2*u]=lazy[u];
lazy[2*u]=lazy[u];
}
if(mx[2*u+1]>lazy[u]){
sum[2*u+1]-=1LL*cnt[2*u+1]*(mx[2*u+1]-lazy[u]);
mx[2*u+1]=lazy[u];
lazy[2*u+1]=lazy[u];
}
lazy[u]=-1;
}
void pushup(int u){
if(mx[2*u]==mx[2*u+1]){
mx[u]=mx[2*u];
cnt[u]=cnt[2*u]+cnt[2*u+1];
se[u]=max(se[2*u], se[2*u+1]);
sum[u]=sum[2*u]+sum[2*u+1];
}
else if(mx[2*u]>mx[2*u+1]){
mx[u]=mx[2*u];
cnt[u]=cnt[2*u];
se[u]=max(se[2*u], mx[2*u+1]);
sum[u]=sum[2*u]+sum[2*u+1];
}
else {
mx[u]=mx[2*u+1];
cnt[u]=cnt[2*u+1];
se[u]=max(mx[2*u], se[2*u+1]);
sum[u]=sum[2*u]+sum[2*u+1];
}
}
void build(int u, int l, int r){
lazy[u]=-1;
if(l==r){
mx[u]=sum[u]=a[l];
cnt[u]=1;
se[u]=-1;
return;
}
int mid=l+r>>1;
build(2*u, l, mid);
build(2*u+1, mid+1, r);
pushup(u);
}
void update(int u, int ql, int qr, int t, int l, int r){
if(ql>r||qr<l||mx[u]<=t)return;
if(ql<=l&&r<=qr&&se[u]<t){
putlazy(u, t);
return;
}
pushdown(u);
int mid=l+r>>1;
update(2*u, ql, qr, t, l, mid);
update(2*u+1, ql, qr, t, mid+1, r);
pushup(u);
}
int getmx(int u, int ql, int qr, int l, int r){
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return mx[u];
pushdown(u);
int mid=l+r>>1;
int ans=0;
ans=max(ans, getmx(2*u, ql, qr, l, mid));
ans=max(ans, getmx(2*u+1, ql, qr, mid+1, r));
return ans;
}
ll getsum(int u, int ql, int qr, int l, int r){
if(ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return sum[u];
pushdown(u);
int mid=l+r>>1;
ll ans=0;
ans+=getsum(2*u, ql, qr, l, mid);
ans+=getsum(2*u+1, ql, qr, mid+1, r);
return ans;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)scanf("%d", &a[i]);
build(1, 1, n);
for(int i=1;i<=m;i++){
int tag;
scanf("%d", &tag);
if(tag==0){
int x, y, t;
scanf("%d%d%d", &x, &y, &t);
update(1, x, y, t, 1, n);
}
else if(tag==1){
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", getmx(1, x, y, 1, n));
}
else {
int x, y;
scanf("%d%d", &x, &y);
printf("%lld\n", getsum(1, x, y, 1, n));
}
}
}
}
而对于bzoj4355来说,基本上是类似的。需要维护区间最小值,区间最小值个数,严格次小值。
对于操作1区间赋值,如果区间不相同,那肯定是把区间变得相同,所以可以暴力更新,而当区间全部相同,区间赋值可以看成区间增,可以打区间增的标记。
对于操作2,可以看做先区间增,再区间与0取max。
类似上一题的操作,唯一区别在于要维护两个懒标记,一个是区间增,另一个是区间取max。
//吉司机线段树
//牛逼啊
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=300005;
int n, m;
ll mn[maxn<<2];
int cnt[maxn<<2];
ll se[maxn<<2];
ll lzcut[maxn<<2];
ll lzadd[maxn<<2];
int a[maxn];
void putcut(int u, ll c){
mn[u]=c;
lzcut[u]=c;
}
void putadd(int u, ll c){
lzadd[u]+=c;
if(lzcut[u]!=-1)lzcut[u]+=c;
mn[u]+=c;
if(se[u]!=-1)se[u]+=c;
}
void pushdown(int u){
if(lzadd[u]){
putadd(2*u, lzadd[u]);
putadd(2*u+1, lzadd[u]);
lzadd[u]=0;
}
if(lzcut[u]!=-1){
if(mn[2*u]<lzcut[u]){
mn[2*u]=lzcut[u];
lzcut[2*u]=lzcut[u];
}
if(mn[2*u+1]<lzcut[u]){
mn[2*u+1]=lzcut[u];
lzcut[2*u+1]=lzcut[u];
}
lzcut[u]=-1;
}
}
void pushup(int u){
if(mn[2*u]==mn[2*u+1]){
mn[u]=mn[2*u];
cnt[u]=cnt[2*u]+cnt[2*u+1];
if(se[2*u]==-1&&se[2*u+1]==-1)se[u]=-1;
else if(se[2*u]==-1)se[u]=se[2*u+1];
else if(se[2*u+1]==-1)se[u]=se[2*u];
else se[u]=min(se[2*u], se[2*u+1]);
}
else if(mn[2*u]<mn[2*u+1]){
mn[u]=mn[2*u];
cnt[u]=cnt[2*u];
if(se[2*u]==-1){
se[u]=mn[2*u+1];
}
else {
se[u]=min(mn[2*u+1], se[2*u]);
}
}
else {
mn[u]=mn[2*u+1];
cnt[u]=cnt[2*u+1];
if(se[2*u+1]==-1){
se[u]=mn[2*u];
}
else {
se[u]=min(mn[2*u], se[2*u+1]);
}
}
}
void build(int u, int l, int r){
lzcut[u]=-1;
lzadd[u]=0;
if(l==r){
mn[u]=a[l];
cnt[u]=1;
se[u]=-1;
return;
}
int mid=(l+r)/2;
build(2*u, l, mid);
build(2*u+1, mid+1, r);
pushup(u);
}
void cover(int u, int ql, int qr, int c, int l, int r){
if(r<ql||qr<l)return;
if(ql<=l&&r<=qr&&se[u]==-1){
putadd(u, (ll)c-mn[u]);
return;
}
int mid=(l+r)/2;
pushdown(u);
cover(2*u, ql, qr, c, l, mid);
cover(2*u+1, ql, qr, c, mid+1, r);
pushup(u);
}
void add(int u, int ql, int qr, int c, int l, int r){
if(r<ql||qr<l||(se[u]==-1&&mn[u]==0&&c<=0))return;
if(ql<=l&&r<=qr){
if(mn[u]+c>=0){
putadd(u, c);
return;
}
else if(se[u]==-1||se[u]+c>0){
putadd(u, c);
putcut(u, 0);
return;
}
}
int mid=(l+r)/2;
pushdown(u);
add(2*u, ql, qr, c, l, mid);
add(2*u+1, ql, qr, c, mid+1, r);
pushup(u);
}
int query(int u, int ql, int qr, int l, int r){
if(r<ql||qr<l)return 0;
if(ql<=l&&r<=qr){
if(mn[u]==0)return cnt[u];
return 0;
}
int mid=(l+r)/2;
int ret=0;
pushdown(u);
ret+=query(2*u, ql, qr, l, mid);
ret+=query(2*u+1, ql, qr, mid+1, r);
return ret;
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
}
build(1, 1, n);
for(int i=1;i<=m;i++){
int tag;
scanf("%d", &tag);
if(tag==1){
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
cover(1, l, r, c, 1, n);
}
else if(tag==2){
int l, r, c;
scanf("%d%d%d", &l, &r, &c);
add(1, l, r, c, 1, n);
}
else {
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(1, l, r, 1, n));
}
}
}