BZOJ4695 最假女选手(势能线段树)
终于体会到初步掌握势能分析思想的重要性了。
一开始看题,感觉套路还是很一般啊qwq。直接在线段树上维护最大值和最小值,每次递归更新的时候,如果不能完全覆盖就暴力递归下去。挺好写的欸
鉴于上次写冒险常数太大的经历,蒟蒻这次来个码风奇特的指针线段树
#include<bits/stdc++.h>
#define RG register
#define R RG int
#define G if(++ip==ie)fread(ip=buf,1,N,stdin)
#define pushup \
s=lc->s+rc->s; \
mn=min(lc->mn,rc->mn); \
mx=max(lc->mx,rc->mx)
#define pushdn \
if(ls!=INF)lc->lset(ls),rc->lset(ls),ls=INF;\
if(la)lc->ladd(la),rc->ladd(la),la=0
using namespace std;
typedef long long LL;
const int N=1<<20,INF=1e9;
char buf[N],*ie=buf+N,*ip=ie-1;
int op,x;
inline int in(){
G;while(*ip<'-')G;
RG bool f=*ip=='-';if(f)G;
R x=*ip&15;
G;while(*ip>'-'){x*=10;x+=*ip&15;G;}
return f?-x:x;
}
struct Node{
Node*lc,*rc;
int l,r,m,mn,mx,ls;LL s,la;
void build(R b,R e){//建树
m=((l=b)+(r=e))>>1;ls=INF;la=0;
if(b==e){
s=mn=mx=in();return;
}
(lc=new Node)->build(l,m);
(rc=new Node)->build(m+1,r);
pushup;
}
inline void lset(R x){//区间覆盖
s=(LL)x*(r-l+1);mn=mx=ls=x;la=0;
}
inline void ladd(R x){//区间加
s+=(LL)x*(r-l+1);mn+=x;mx+=x;la+=x;
}
void add(R b,R e){//操作1
if(l==b&&r==e)return this->ladd(x);
pushdn;
if(e<=m)lc->add(b,e);
else if(b>m)rc->add(b,e);
else lc->add(b,m),rc->add(m+1,e);
pushup;
}
void upd(R b,R e){//操作2/3
if(op&1?mx<=x:mn>=x)return;
if(l==b&&r==e&&(op&1?mn>=x:mx<=x))return this->lset(x);
pushdn;
if(e<=m)lc->upd(b,e);
else if(b>m)rc->upd(b,e);
else lc->upd(b,m),rc->upd(m+1,e);
pushup;
}
LL qrys(R b,R e){//操作4
if(l==b&&r==e)return s;
pushdn;
if(e<=m)return lc->qrys(b,e);
if(b> m)return rc->qrys(b,e);
return lc->qrys(b,m)+rc->qrys(m+1,e);
}
int qrym(R b,R e){//操作5/6
if(l==b&&r==e)return op&1?mx:mn;
pushdn;
if(e<=m)return lc->qrym(b,e);
if(b> m)return rc->qrym(b,e);
if(op&1)return max(lc->qrym(b,m),rc->qrym(m+1,e));
return min(lc->qrym(b,m),rc->qrym(m+1,e));
}
};
int main(){
RG Node rt;rt.build(1,in());
for(R m=in(),l,r;m;--m){
op=in();l=in();r=in();
if(op<=3)x=in(),op==1?rt.add(l,r):rt.upd(l,r);
else if(op==4)printf("%lld\n",rt.qrys(l,r));
else printf("%d\n",rt.qrym(l,r));
}
return 0;
}
然后就过了?!然后拿了BZOJ rank1?!
然后这是暴力。
很显然我们还是要着眼于势能分析。下面开始瞎逼逼,只讨论取min,因为取max是一回事。
随便定义一个线段树节点的势函数为其管辖区间内不同数的个数。这样初始势函数总和就是\(O(n\log n)\)级别的。
如果只记区间最小值,那么显然如果做一次修改,它可能还是最小值,势函数并没有减小。
那要怎么好呢?记次小值!如果\(x\)大于最小值而小于次小值,那么我们打上一个区间修改最小值的标记;如果\(x\)大于等于次小值,那么肯定不同数的个数会减少,此处每额外展开一次暴力递归势函数就会至少减小\(1\),复杂度就是对的了。
注意到这里要维护和,于是我们在记最小值的时候顺便维护区间内最小值的个数。
如果只有区间min/max,那么复杂度就是一个\(\log\)的了。
可是这里的操作既有min又有max还有区间加,这时要另外分析。吉老师好像在论文里说有一个比较松的\(m\log^2\)上界,不过蒟蒻发现还不是很懂势能分析那一套理论就无法接着逼逼下去了。
蒟蒻注意到这样一句话
代码实现较长,但理解本做法后编程复杂度并不高。
然而——这就是一个可以拍几十次次次都WA,重构三次代码的线段树吗?
细节问题:
区间加,区间加最小值,区间加最大值按理来说似乎是独立的,可是蒟蒻重构了两边最后放区间加标记的代码就是调不出来,无奈之下最先放了区间加,然后又因为玄学问题爆int(本来应该只有区间和要开longlong的啊)
cz_xuyixuan队爷写的是先放加min/max再放区间加的。
区间加min的时候,可能还要考虑对区间最大值/次大值的影响(如果区间只有一个或者两个不同的数的时候)。区间加max同理。
区间加min/max的时候,注意判断最值的子树来源后再放。
至于代码什么的会让你痛不欲生。。。蒟蒻硬是用define写函数把人人要写4KB+的代码缩成了3KB。。。此坑慎入!
因为不得已开longlong比
#include<bits/stdc++.h>
#define RG register
#define R RG int
#define I inline void
#define G if(++ip==ie)fread(ip=buf,1,N,stdin)
using namespace std;
typedef long long LL;
const int N=1<<19,INF=1e9;
char buf[N],*ie=buf+N,*ip=ie-1;
int x;
inline int in(){
G;while(*ip<'-')G;
RG bool f=*ip=='-';if(f)G;
R x=*ip&15;
G;while(*ip>'-'){x*=10;x+=*ip&15;G;}
return f?-x:x;
}
#define int LL//请无视
struct Node{
Node*lc,*rc;
int l,r,m,mn1,mn2,mnc,mx1,mx2,mxc,lmn,lmx,lad,s;
I up(){//维护最值和次值,小心点写
s=lc->s+rc->s;
if(lc->mn1<rc->mn1)
mn1=lc->mn1,mnc=lc->mnc,mn2=min(lc->mn2,rc->mn1);
else if(lc->mn1>rc->mn1)
mn1=rc->mn1,mnc=rc->mnc,mn2=min(rc->mn2,lc->mn1);
else mn1=lc->mn1,mnc=lc->mnc+rc->mnc,mn2=min(lc->mn2,rc->mn2);
if(lc->mx1>rc->mx1)
mx1=lc->mx1,mxc=lc->mxc,mx2=max(lc->mx2,rc->mx1);
else if(lc->mx1<rc->mx1)
mx1=rc->mx1,mxc=rc->mxc,mx2=max(rc->mx2,lc->mx1);
else mx1=lc->mx1,mxc=lc->mxc+rc->mxc,mx2=max(lc->mx2,rc->mx2);
}
I dnlad(R x){//区间加
mn1+=x;if(mn2!= INF)mn2+=x;
mx1+=x;if(mx2!=-INF)mx2+=x;
s+=x*(r-l+1);lad+=x;
}
I dnlmn(R x){//区间加最小值
if(mn1==mx1)mx1+=x;if(mn1==mx2)mx2+=x;//注意特判
s+=x*mnc;mn1+=x;lmn+=x;
}
I dnlmx(R x){//区间加最大值
if(mx1==mn1)mn1+=x;if(mx1==mn2)mn2+=x;
s+=x*mxc;mx1+=x;lmx+=x;
}
I dn(){//放标记
if(lad)lc->dnlad(lad),rc->dnlad(lad),lad=0;
if(lmn){
if(lc->mn1<=rc->mn1)lc->dnlmn(lmn);
if(lc->mn1>=rc->mn1)rc->dnlmn(lmn);
lmn=0;
}
if(lmx){
if(lc->mx1>=rc->mx1)lc->dnlmx(lmx);
if(lc->mx1<=rc->mx1)rc->dnlmx(lmx);
lmx=0;
}
}
I build(R b,R e){//建树
m=((l=b)+(r=e))>>1;lmn=lmx=lad=0;
if(l==r){
s=mn1=mx1=in();mnc=mxc=1;
mn2=INF;mx2=-INF;
return;
}
(lc=new Node)->build(l,m);
(rc=new Node)->build(m+1,r);
this->up();
}
#define sum(x,y) x+y//修改函数模板
#define upd(Fun,Lim,Req,Tag) \
I Fun(R b,R e){ \
if(Lim)return; \
if(l==b&&r==e Req)return this->Tag; \
this->dn(); \
if(e<=m)lc->Fun(b,e); \
else if(b>m)rc->Fun(b,e); \
else lc->Fun(b,m),rc->Fun(m+1,e); \
this->up(); \
}//查询函数模板
#define qry(Fun,Typ,Ret,Opt) \
inline Typ Fun(R b,R e){ \
if(l==b&&r==e)return Ret; \
this->dn(); \
if(e<=m)return lc->Fun(b,e); \
if(b>m)return rc->Fun(b,e); \
return Opt(lc->Fun(b,m),rc->Fun(m+1,e));\
}
upd(us,0,,dnlad(x));
upd(umn,mn1>=x,&&mn2>x,dnlmn(x-mn1));
upd(umx,mx1<=x,&&mx2<x,dnlmx(x-mx1));
qry(qs,LL,s,sum);
qry(qmx,int,mx1,max);
qry(qmn,int,mn1,min);
};
#undef int
int main(){
RG Node rt;rt.build(1,in());
for(R m=in(),op,l,r;m;--m){
op=in();l=in();r=in();
if(op<=3)x=in();
if(op==1)rt.us (l,r);
if(op==2)rt.umn(l,r);
if(op==3)rt.umx(l,r);
if(op==4)printf("%lld\n",rt.qs (l,r));
if(op==5)printf("%lld\n",rt.qmx(l,r));
if(op==6)printf("%lld\n",rt.qmn(l,r));
}
return 0;
}
BZOJ4695 最假女选手(势能线段树)的更多相关文章
- 2018.07.27 bzoj4695: 最假女选手(线段树)
传送门 线段树好题 支持区间加,区间取min" role="presentation" style="position: relative;"> ...
- bzoj 4695 最假女选手 吉利线段树
最假女选手 Time Limit: 50 Sec Memory Limit: 128 MBSubmit: 480 Solved: 118[Submit][Status][Discuss] Desc ...
- bzoj4695 最假女选手(势能线段树/吉司机线段树)题解
题意: 已知\(n\)个数字,进行以下操作: \(1.\)给一个区间\([L,R]\) 加上一个数\(x\) \(2.\)把一个区间\([L,R]\) 里小于\(x\) 的数变成\(x\) \(3.\ ...
- bzoj4695 最假女选手
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4695 [题解] SegmentTree beats!(见jiry_2论文/营员交流) 考虑只 ...
- [BZOJ4695]最假女选手:segment tree beats!
分析 segment tree beats!模板题. 看了gxz的博客突然发现自己写的mxbt和mnbt两个标记没用诶. 代码 #include <bits/stdc++.h> #defi ...
- 有趣的线段树模板合集(线段树,最短/长路,单调栈,线段树合并,线段树分裂,树上差分,Tarjan-LCA,势能线段树,李超线段树)
线段树分裂 以某个键值为中点将线段树分裂成左右两部分,应该类似Treap的分裂吧(我菜不会Treap).一般应用于区间排序. 方法很简单,就是把分裂之后的两棵树的重复的\(\log\)个节点新建出来, ...
- 洛谷P4891 序列(势能线段树)
洛谷题目传送门 闲话 考场上一眼看出这是个毒瘤线段树准备杠题,发现实在太难调了,被各路神犇虐哭qwq 考后看到各种优雅的暴力AC......宝宝心里苦qwq 思路分析 题面里面是一堆乱七八糟的限制和性 ...
- BZOJ5312 冒险(势能线段树)
BZOJ题目传送门 表示蒟蒻并不能一眼看出来这是个势能线段树. 不过仔细想想也并非难以理解,感性理解一下,在一个区间里又与又或,那么本来不相同的位也会渐渐相同,线段树每个叶子节点最多修改\(\log ...
- Can you answer these queries?(HDU4027+势能线段树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4027 题目: 题意:n个数,每次区间更新将其数值变成它的根号倍(向下取整),区间查询数值和. 思路:易 ...
随机推荐
- YCSB报": No such file or directory"异常
异常信息如下: 文件路径.权限都没有问题. 上网遍寻无果,安装流程与官网一致,开始怀疑是环境问题,后来用别人能用的YCSB复制到本地,却能正常运行. 后来修改了ycsb文件,加了个空格,保存退出,再运 ...
- JMeter中返回Json数据的处理方法(转)
Json 作为一种数据交换格式在网络开发,特别是 Ajax 与 Restful 架构中应用的越来越广泛.而 Apache 的 JMeter 也是较受欢迎的压力测试工具之一,但是它本身没有提供对于 Js ...
- [转帖]再次提醒Google Chrome用户应尽快升级浏览器到72.0.3626.121
再次提醒Google Chrome用户应尽快升级浏览器到72.0.3626.121 转帖地址: https://www.cnbeta.com/articles/tech/825591.htm 国内离线 ...
- null值经过强转会怎样?
null还是null,类型不会改变的,也不会报错!
- python之路--MySQL数据库初识
一 . MySQL安装 # 下载MySQL地址 https://dev.mysql.com/downloads # 要选稳定的,不要选最新的,稳定的就是半年以上没有出现过bug 现在5.6.43为绝大 ...
- Linux基础学习(13)--Linux系统管理
第十三章——Linux系统管理 一.进程管理 1.进程查看: (1)进程简介:进程是正在执行的一个程序或命令,每一个进程都是一个运行的实体,都有自己的地址空间,并占用一定的系统资源. (2)进程管理的 ...
- 思维导图,UML图,程序流程图制作从入门到精通
工具: https://www.processon.com/ 第一 用例图 第二 时序图 第三 流程图
- Android SDK Tools 24.3.2 Build脚本Bug
如下图版本: 在%Android_home%\tools\ant\build.xml中, 在483行附近, 少了aidl,aapt,dx, zipalign四个变量的声明. 加上就OK了. <p ...
- 转 在PowerDesigner的PDM图形窗口中显示数据列的中文注释
Name是名称(字段描述),Code是字段名称,Comment是注释名称,ER图中显示的是Name.一般设计时,Name跟comment都设计成描述, 而设计时候常把comment写成中文,name保 ...
- MySQL列类型选择
比如年龄这个字段可以使用 1990-03-15 也可以用 19900315表示在列类型上可以选择 char 和 int:如果一个字段可以选择多种类型,尽量选择一个更快的类型:字段类型优先级 ...