线段树详解:https://blog.csdn.net/zearot/article/details/48299459
符合区间加法的例子:
数字之和——总数字之和 = 左区间数字之和 + 右区间数字之和
最大公因数(GCD)——总GCD = gcd( 左区间GCD , 右区间GCD );
最大值——总最大值=max(左区间最大值,右区间最大值)
不符合区间加法的例子:
众数——只知道左右区间的众数,没法求总区间的众数
01序列的最长连续零——只知道左右区间的最长连续零,没法知道总的最长连续零
点修改 修改次数的最大值为层数\(\lfloor log_2(n-1)\rfloor+2\) 复杂度\(O(log2(n))\)
区间查询 \(n\ge3\)时,一个\([1,n]\)的线段树可以将\([1,n]\)的任意子区间\([L,R]\)分解为不超过\(2\lfloor log_2(n-1)\rfloor\)个子区间 \(O(log2(n))\)
标记有相对标记和绝对标记之分:
相对标记是将区间的所有数+a之类的操作,标记之间可以共存,跟打标记的顺序无关(跟顺序无关才是重点)。
所以,可以在区间修改的时候不下推标记,留到查询的时候再下推。
注意:如果区间修改时不下推标记,那么PushUp函数中,必须考虑本节点的标记。
而如果所有操作都下推标记,那么PushUp函数可以不考虑本节点的标记,因为本节点的标记一定已经被下推了(也就是对本节点无效了)
绝对标记是将区间的所有数变成a之类的操作,打标记的顺序直接影响结果,
所以这种标记在区间修改的时候必须下推旧标记,不然会出错。
CF527C Glass Carving
一块w*h的玻璃,每次横着切一刀(H)或者竖着切一刀(V),没有两次相同的切割,求最大的矩形碎片面积。 样例中第一行是w,h(玻璃大小)和n(切割次数),字母后的数字表示距下边缘(H)/左边缘(V)的距离
用01序列表示每个点是否被切割
最长的长就是长的最长连续0的数量+1 最长的宽就是宽的最长连续0的数量+1
用线段树维护最长连续零
最长连续0不满足区间加法 于是维护左端开始最长连续0 右端开始最长连续0 (很像维护最大字段和那个
==其实我的代码里sum是多余的
还有set可以做这题
#include
using namespace std;
#define ll long long
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define Abs(x) ((x)<0?-(x):(x))
#define ls (o<<1)
#define rs (o<<1|1)
const int N=2e5+5,M=100+5,inf=0x3f3f3f3f;
int w,h,n;
template void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
struct SegmentTree{int lmx,rmx,mx,sum;bool pu;}t[N<<2][2];
void pup(int o,int id){
t[o][id].pu=t[ls][id].pu&t[rs][id].pu;
t[o][id].sum=t[ls][id].sum+t[rs][id].sum;
t[o][id].mx=Max(t[ls][id].rmx+t[rs][id].lmx,Max(t[ls][id].mx,t[rs][id].mx));
t[o][id].lmx=t[ls][id].pu?t[ls][id].sum+t[rs][id].lmx:t[ls][id].lmx;
t[o][id].rmx=t[rs][id].pu?t[rs][id].sum+t[ls][id].rmx:t[rs][id].rmx;
}/*
void pup(int o,int id){
t[o][id].pu=t[ls][id].pu&t[rs][id].pu;
t[o][id].mx=Max(t[ls][id].rmx+t[rs][id].lmx,Max(t[ls][id].mx,t[rs][id].mx));
t[o][id].lmx=t[ls][id].pu?t[ls][id].lmx+t[rs][id].lmx:t[ls][id].lmx;
t[o][id].rmx=t[rs][id].pu?t[rs][id].rmx+t[ls][id].rmx:t[rs][id].rmx;
}*/
void mdf(int o,int l,int r,int x,int id){
if(l==r){t[o][id]=(SegmentTree){0,0,0,t[o][id].sum,0};return;}
int mid=l+r>>1;
if(x<=mid) mdf(ls,l,mid,x,id);
else mdf(rs,mid+1,r,x,id);
pup(o,id);
}
void build(int o,int l,int r,int id){
if(l==r){t[o][id]=(SegmentTree){1,1,1,1,1};return;}
int mid=l+r>>1;
build(ls,l,mid,id),build(rs,mid+1,r,id);
pup(o,id);
}
int main(){
freopen("in.txt","r",stdin);
rd(w),rd(h),rd(n);
build(1,1,w-1,1),build(1,1,h-1,0);
for(int i=1;i<=n;++i){
char opt;int x;
scanf(" %c%d",&opt,&x);
if(opt=='H') mdf(1,1,h-1,x,0);
else mdf(1,1,w-1,x,1);
printf("%lld\n",(ll)(t[1][1].mx+1)*(t[1][0].mx+1));
}
return 0;
}
莫队
ovo里的树上莫队:https://blog.csdn.net/kuribohG/article/details/41458639
询问左端点所在的分块的序号为第一关键字,右端点的大小为第二关键字
莫队从入门到黑题:https://www.cnblogs.com/WAMonster/p/10118934.html
洛谷日报:https://www.luogu.org/blog/codesonic/Mosalgorithm
代码主要看的yyb的
首先一定要明确莫队的适用范围,
求解多次询问区间的问题
如果已知(l..r)的答案,
能够在O(1)(或者很小的时间复杂度???)推出(l-1..r)(l+1..r)(l..r-1)(l..r+1)的时候
适用莫队
莫队高效的原因在于
并不是反复计算某一个区间
因为区间存在重合的部分
因此重合的部分不用反复计算,
莫队的核心就是重复利用这些重合了的部分来提高效率。
带修改的莫队做法和普通莫队并没有什么不同
只是每一个询问还要储存上一次修改操作的编号
一样的离线,排序
每一次操作之前首先对队列进行修改和还原,
然后其余的操作和普通的莫队就一模一样了。
luogu2709小B的询问
P2709 小B的询问
求一个区间中每个数出现次数的平方和
#include
using namespace std;
#define ll long long
const int N=50000+5,M=100+5,inf=0x3f3f3f3f;
int n,k,m,block,a[N],cnt[N];
ll Ans,ans[N];
template void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
struct node{int l,r,id,bl;}q[N];
bool cmp(node A,node B){return A.bl==B.bl?A.rq[i].l) count(--l,1);
while(rq[i].r) count(r--,-1);
ans[q[i].id]=Ans;
}
for(int i=1;i<=m;++i) printf("%lld\n",ans[i]);
return 0;
}
国家集训队 小Z的袜子
P1494 国家集训队]小Z的袜子 bzoj2038
已知区间\([l,r]\)中袜子出现次数为\(a,b,c...\)
则为\((a*(a-1)/2+b*(b-1)+c*(c-1)/2..)/((r-l+1)*(r-l)/2)\)
\(=(a^2+b^2+c^2+...+(r-l+1))/((r-l+1)*(r-l))\)
然后就能转化为上一道题辽(系统自带表情好可爱啊啊啊啊
排序的一个优化?
排序改成:
bool cmp(node A,node B){return (A.bl^B.bl)?A.blB.r);}
就快了好多
也就是说,对于左端点在同一奇数块的区间,右端点按升序排列,反之降序。这个东西也是看着没用,但实际效果显著。
它的主要原理便是右指针跳完奇数块往回跳时在同一个方向能顺路把偶数块跳完,然后跳完这个偶数块又能顺带把下一个奇数块跳完。理论上主算法运行时间减半,实际情况有所偏差。
(不过能优化得很爽就对了)
#include
using namespace std;
#define ll long long
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define Abs(x) ((x)<0?-(x):(x))
#define ls (o<<1)
#define rs (o<<1|1)
const int N=50000+5,M=100+5,inf=0x3f3f3f3f;
int n,m,block,a[N],cnt[N];
ll Ans,aa[N],ab[N];
template void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
struct node{int l,r,id,bl;}q[N];
bool cmp(node A,node B){return (A.bl^B.bl)?A.blB.r);}
//bool cmp(node A,node B){return A.bl==B.bl?A.rb) swap(a,b);
return !a?b:gcd(b%a,a);
}
int main(){
freopen("in.txt","r",stdin);
rd(n),rd(m),block=sqrt(n);
for(int i=1;i<=n;++i) rd(a[i]);
for(int i=1;i<=m;++i) rd(q[i].l),rd(q[i].r),q[i].id=i,q[i].bl=(q[i].l-1)/block+1;
sort(q+1,q+m+1,cmp);
int l=1,r=0;Ans=0ll;
for(int i=1;i<=m;++i){
while(lq[i].l) count(--l,1);
while(rq[i].r) count(r--,-1);
if(q[i].l==q[i].r) {aa[q[i].id]=0,ab[q[i].id]=1;continue;}
ll x=q[i].r-q[i].l+1;
aa[q[i].id]=Ans-x,ab[q[i].id]=x*(x-1);
}
for(int i=1;i<=m;++i){
ll x=gcd(aa[i],ab[i]);
if(x>0) printf("%lld/%lld\n",aa[i]/x,ab[i]/x);
else printf("%lld/%lld\n",aa[i],ab[i]);
}
return 0;
}
spfa打挂身败名裂...
第二段路
#include
using namespace std;
#define ll long long
const int N=5000+10,M=1e5+50,inf=0x3f3f3f3f;
int n,m;
template void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
int head[N],tot=0;
struct edge{int v,w,nxt;}e[M<<1];
void add(int u,int v,int w){
e[++tot]=(edge){v,w,head[u]},head[u]=tot;
}
struct node{
int dis,id;
};
int dis[N][2];bool in;
queueq;
void spfa(){
memset(dis,inf,sizeof(dis));
dis[1][0]=0;q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u],v,w;i;i=e[i].nxt){
v=e[i].v,w=e[i].w,in=0;
if(dis[v][0]>dis[u][0]+w) dis[v][1]=dis[v][0],dis[v][0]=dis[u][0]+w,in=1;
if(dis[v][0]dis[u][1]+w) dis[v][1]=dis[u][1]+w,in=1;
if(in) q.push(v);
}
}
}
int main(){
freopen("in.txt","r",stdin);
//freopen("and.out","w",stdout);
rd(n),rd(m);
for(int i=1,u,v,w;i<=m;++i) rd(u),rd(v),rd(w),add(u,v,w),add(v,u,w);
spfa();
printf("%d",dis[n][1]);
return 0;
}
lyd书上动态开点的一些操作
struct SegmentTree{
int lc,rc,dat;
}t[size<<1];
int build(){//新建一个节点
t[++tot].lc=t[tot].rc=t[tot].dat=0;
return tt;
}
void insert(int o,int l,int r,int val,int delta){
if(l==r){t[o].dat+=delta;return;}
int mid=l+r>>1;
if(val<=mid){
if(!t[o].lc) t[o].lc=build();
insert(t[o].lc,l,mid,val,delta);
}
else{
if(!t[o].rc) t[o].rc=build();
insert(t[o].rc,mid+1,r,val,delta);
}
t[o].dat=Max(t[t[o].lc].dat,t[t[o],rc].dat);
}
int main(){
tot=0;
root=build();
insert(root,1,n,val,delta);
return 0;
}
==突然不理解为啥之前我死都不能理解动态开点 害挺简单的
bzoj4636 蒟蒻的数列
bzoj4636
DCrusher有一个数列,初始值均为0,他进行N次操作,每次将数列[a,b)这个区间中所有比k小的数改为k,他想知道N次操作后数列中所有元素的和。
第一行一个整数N,然后有N行,每行三个正整数a、b、k。
N<=40000 , a、b、k<=10^9
一道模板?
==动态开点 然后维护标记 所有操作完之后这段区间内所有数肯定都等于最大的那一次k 就是相当于打个标记
#include
using namespace std;
#define ll long long
#define Max(x,y) ((x)>(y)?(x):(y))
#define lson (o<<1)
#define rson (o<<1|1)
const int N=40000+10,M=1e9,inf=0x3f3f3f3f;
int n,m,rt,tot;
ll ans=0;
template void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
struct node{int ls,rs,dat;}t[N<<2];
void upd(int &o,int l,int r,int x,int y,int k){
if(l>y||r>1;
upd(t[o].ls,l,mid,x,y,k),upd(t[o].rs,mid+1,r,x,y,k);
}
void query(int o,int l,int r){
if(!t[o].ls&&!t[o].rs){ans+=(ll)(r-l+1)*t[o].dat;return;}
if(t[o].ls) t[t[o].ls].dat=Max(t[t[o].ls].dat,t[o].dat);
if(t[o].rs) t[t[o].rs].dat=Max(t[t[o].rs].dat,t[o].dat);
int mid=l+r>>1;
if(t[o].ls) query(t[o].ls,l,mid);
if(t[o].rs) query(t[o].rs,mid+1,r);
if(!t[o].ls) ans+=(ll)(mid-l+1)*t[o].dat;
if(!t[o].rs) ans+=(ll)(r-mid)*t[o].dat;
}
int main(){
freopen("in.txt","r",stdin);
//freopen("and.out","w",stdout);
rd(n);
for(int i=1,x,y,k;i<=n;++i){
rd(x),rd(y),rd(k);
if(x<=--y) upd(rt,1,M,x,y,k);
}
query(rt,1,M);
printf("%lld",ans);
return 0;
}