树状数组 线段树
考完试了,找了几个数据结构题来愉(zuo)悦(jian)身(zi)心(ji)。
泪,射了出来。
One : CF438D & P4145
题意:
给定一个长度为\(n\)的数列和\(m\)个操作,一类操作是对位于\([l,r]\)范围内的所有的正整数单个取模或开方,另一类操作是查询\([l,r]\)的区间和。
思路:
没有思路。
以取模为例,设模数为\(p\),原正整数为\(x\),观察到若\(x<p\),则取模后的结果为\(x\),若\(x\ge p\),则取模后的结果一定小于\(\frac{x}{2}\)(易证),于是我们好像明白了点什么。
那就是\(x\)至多会被取模\(log\ x\)次。
根据这个性质,我们可以对在线段树同时维护区间和与最大值,在修改时若发现该区间的最大值就已经小于\(p\),则可以直接返回,否则暴力对区间进行修改并向上更新维护信息,时间花费上是允许的。
开方同理,只需要判断最大值是否小于等于\(1\)即可。
这告诉我们碰见这种神必题要先打暴力再进行优化不要上来就去想所谓正解,因为暴力即正解。
代码:
#include <bits/stdc++.h>
#define int long long
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int maxn=300000;
int n,m,a[maxn];
struct EE{
int l,r,sum,maxx;
}tr[maxn<<2];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void pushup(int rt){
tr[rt].sum=tr[lson].sum+tr[rson].sum;
tr[rt].maxx=max(tr[lson].maxx,tr[rson].maxx);
}
void build(int rt,int l,int r){
tr[rt].l=l;
tr[rt].r=r;
if(l==r){
tr[rt].sum=a[l];
tr[rt].maxx=a[l];
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
void update(int rt,int pos,int val){
if(tr[rt].l==tr[rt].r){
tr[rt].sum=val;
tr[rt].maxx=val;
return;
}
int mid=(tr[rt].l+tr[rt].r)>>1;
if(pos<=mid) update(lson,pos,val);
else update(rson,pos,val);
pushup(rt);
}
void modify(int rt,int l,int r,int mod){
if(tr[rt].maxx<mod) return;
if(tr[rt].l==tr[rt].r){
tr[rt].sum%=mod;
tr[rt].maxx%=mod;
return;
}
int mid=(tr[rt].l+tr[rt].r)>>1;
if(l<=mid) modify(lson,l,r,mod);
if(r>mid) modify(rson,l,r,mod);
pushup(rt);
}
int query(int rt,int l,int r){
if(l<=tr[rt].l&&tr[rt].r<=r) return tr[rt].sum;
int mid=(tr[rt].l+tr[rt].r)>>1;
int sum=0;
if(l<=mid) sum+=query(lson,l,r);
if(r>mid) sum+=query(rson,l,r);
return sum;
}
signed main(){
n=read();m=read();
for(int i=1;i<=n;++i) a[i]=read();
build(1,1,n);
while(m--){
int opt=read();
if(opt==1){
int l=read(),r=read();
printf("%lld\n",query(1,l,r));
}else if(opt==2){
int l=read(),r=read(),p=read();
modify(1,l,r,p);
}else{
int pos=read(),val=read();
update(1,pos,val);
}
}
return 0;
}
好了要放假了,假期再补。
好了假期没有补,咱们现在补。
Two:P4514
题意:
让你冲一发支持区间修改和区间查询的二维数据结构。
思路:
二维线段树根据题目的数据范围显然会炸,那我们想二维树状数组。
首先我们来想一下支持区间修改和区间查询的一维树状数组该怎么冲写。
我们用\(d\)表示差分数组,用\(p\)表示某个位置,用\(s\)表示前缀和数组,那么显然易得此式:
然后我们发现在该前缀和中,\(d[1]\)出现了\(p\)次,\(d[2]\)出现了\(p-1\)次...所以有
于是就能很愉快地开分别开两个一维树状数组维护了。
我们再看二维(用\(r\)表示横坐标,用\(c\)表示纵坐标):
我们很难过地发现这个式子是\(O(n^4)\),太不好了,所以考虑化式子,类比我们刚才推出的一维树状数组,\(d[1][1]\)出现了\(r\times c\)次,\(d[1][2]\)出现了\(r\times (c-1)\)次...所以有:
于是就能很愉快地开分别开四个二维树状数组维护了。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=3000;
int n,m;
int tr[4][maxn][maxn];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline int lowbit(int x){
return x&(-x);
}
inline void update(int x,int y,int val,int idx){
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
tr[idx][i][j]+=val;
}
inline void modify(int X1,int Y1,int X2,int Y2,int val){
update(X1,Y1,val,0);update(X1,Y2+1,-val,0);
update(X2+1,Y1,-val,0);update(X2+1,Y2+1,val,0);
update(X1,Y1,val*(X1-1)*(Y1-1),1);update(X1,Y2+1,-val*(X1-1)*Y2,1);
update(X2+1,Y1,-val*X2*(Y1-1),1);update(X2+1,Y2+1,val*X2*Y2,1);
update(X1,Y1,val*(X1-1),2);update(X1,Y2+1,-val*(X1-1),2);
update(X2+1,Y1,-val*X2,2);update(X2+1,Y2+1,val*X2,2);
update(X1,Y1,val*(Y1-1),3);update(X1,Y2+1,-val*Y2,3);
update(X2+1,Y1,-val*(Y1-1),3);update(X2+1,Y2+1,val*Y2,3);
}
inline int queryt(int x,int y,int idx){
int res=0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
res+=tr[idx][i][j];
return res;
}
inline int query(int X,int Y){
return X*Y*queryt(X,Y,0)+queryt(X,Y,1)-X*queryt(X,Y,3)-Y*queryt(X,Y,2);
}
inline int queryval(int X1,int Y1,int X2,int Y2){
return query(X2,Y2)-query(X2,Y1-1)-query(X1-1,Y2)+query(X1-1,Y1-1);
}
int main(){
char opt;
scanf(" %c",&opt);
n=read();m=read();
while(scanf(" %c",&opt)!=EOF){
if(opt=='L'){
int a=read(),b=read(),c=read(),d=read(),val=read();
modify(a,b,c,d,val);
}else{
int a=read(),b=read(),c=read(),d=read();
printf("%d\n",queryval(a,b,c,d));
}
}
return 0;
}
Three:P4315
题意:
给定一个有\(n\)个节点的树和\(n-1\)条边权,并对其进行单边修改和从\(u\)到\(v\)路径上的区间增加,区间修改和区间最大值查询。
思路:
树剖+线段树。
然而这个神必题有两种操作,一种是把一段区间推平,另一种是把一个区间的值增加。于是我们可以维护两个\(lazy\)标记,当这一段区间被推平时,修改\(lazy\)的同时把\(adlazy\)清零,当这一段区间被增加时,直接增加\(adlazy\)。
\(pushdown\)时,若\(lazy\)和\(adlzy\)都不为\(0\),就把两个儿子先推平后增加,否则就直接下放单个标记即可。
要特别注意几点:
-
单独下放\(lazy\)标记时儿子的\(adlazy\)要清零。
-
单独下放\(adlazy\)标记时儿子的\(adlazy\)要加等。
-
\(lazy\)标记和\(adlazy\)标记共同下放时儿子的\(adlazy\)要直等。
成功消耗一个多小时的宝贵生命。
#include <bits/stdc++.h>
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
const int maxn=200100;
//只能尝试一下时间戳力
int n,cnt,tot,funtimer,head[maxn];
int siz[maxn],f[maxn],dep[maxn],vals1[maxn],vals2[maxn];
int dfn[maxn],top[maxn],son[maxn],bel[maxn];
struct ED{
int to,next,w;
}e[maxn<<1];
struct EE{
int l,r,maxx,lazy,adlazy;
}tr[maxn<<2];
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^48);
ch=getchar();
}
return s*w;
}
inline void add(int u,int v,int w){
e[++cnt].to=v;
e[cnt].next=head[u];
e[cnt].w=w;
head[u]=cnt;
}
inline void find_heavy(int u,int depth){
top[u]=son[u]=0;
siz[u]=1;
dep[u]=depth;
int maxsiz=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to,w=e[i].w;
if(dep[v]) continue;
find_heavy(v,depth+1);
siz[u]+=siz[v],f[v]=u,vals1[v]=w;bel[i]=v;
if(siz[v]>maxsiz){
maxsiz=siz[v];
son[u]=v;
}
}
}
inline void pours(int u,int anc){
top[u]=anc;
dfn[u]=++tot;
vals2[tot]=vals1[u];
if(son[u]) pours(son[u],anc);
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==son[u]||f[u]==v) continue;
pours(v,v);
}
}
inline void pushup(int rt){
tr[rt].maxx=max(tr[lson].maxx,tr[rson].maxx);
}
inline void pushdown(int rt){
if(tr[rt].lazy&&tr[rt].adlazy){
int lz=tr[rt].lazy,adlz=tr[rt].adlazy;
tr[rt].lazy=0;
tr[rt].adlazy=0;
tr[lson].maxx=lz+adlz;
tr[rson].maxx=lz+adlz;
tr[lson].lazy=lz;
tr[rson].lazy=lz;
tr[lson].adlazy=adlz;
tr[rson].adlazy=adlz;
return;
}
if(tr[rt].lazy){
int lz=tr[rt].lazy;
tr[rt].lazy=0;
tr[lson].maxx=lz;
tr[rson].maxx=lz;
tr[lson].lazy=lz;
tr[rson].lazy=lz;
tr[lson].adlazy=0;
tr[rson].adlazy=0;
return;
}
if(tr[rt].adlazy){
int adlz=tr[rt].adlazy;
tr[rt].adlazy=0;
tr[lson].maxx+=adlz;
tr[rson].maxx+=adlz;
tr[lson].adlazy+=adlz;
tr[rson].adlazy+=adlz;
return;
}
}
inline void build(int rt,int l,int r){
tr[rt].l=l;
tr[rt].r=r;
if(l==r){
tr[rt].maxx=vals2[l];
return;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
inline void modify(int rt,int l,int r,int val,int pos){
if(l<=tr[rt].l&&tr[rt].r<=r){
if(pos==1){
tr[rt].lazy=val;
tr[rt].adlazy=0;
tr[rt].maxx=val;
}else{
tr[rt].adlazy+=val;
tr[rt].maxx+=val;
}
return;
}
int mid=(tr[rt].l+tr[rt].r)>>1;
pushdown(rt);
if(l<=mid) modify(lson,l,r,val,pos);
if(r>mid) modify(rson,l,r,val,pos);
pushup(rt);
return;
}
inline int query(int rt,int l,int r){
if(l<=tr[rt].l&&tr[rt].r<=r) return tr[rt].maxx;
int mid=(tr[rt].l+tr[rt].r)>>1;
pushdown(rt);
int maxx=-1;
if(l<=mid) maxx=max(maxx,query(lson,l,r));
if(r>mid) maxx=max(maxx,query(rson,l,r));
return maxx;
}
inline int querymaxx(int u,int v){
int maxx=-1;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
maxx=max(maxx,query(1,dfn[top[u]],dfn[u]));
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
maxx=max(maxx,query(1,dfn[u]+1,dfn[v]));
return maxx;
}
inline void modiend(int u,int v,int val,int pos){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
modify(1,dfn[top[u]],dfn[u],val,pos);
u=f[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
modify(1,dfn[u]+1,dfn[v],val,pos);
}
int main(){
n=read();
for(int i=1;i<=n-1;++i){
int x=read(),y=read(),ws=read();
add(x,y,ws);add(y,x,ws);
}
find_heavy(1,1);
pours(1,1);
build(1,1,n);
char opt[10];
scanf("%s",opt);
while(opt[0]!='S'){
if(opt[0]=='C'&&opt[1]=='h'){
int k=read(),w=read(),pos=0;
if(!bel[k*2-1]) pos=dfn[bel[k*2]];
else pos=dfn[bel[k*2-1]];
modify(1,pos,pos,w,1);
}else if(opt[0]=='C'&&opt[1]=='o'){
int u=read(),v=read(),w=read();
modiend(u,v,w,1);
}else if(opt[0]=='A'){
int u=read(),v=read(),w=read();
modiend(u,v,w,2);
}else{
int u=read(),v=read();
printf("%d\n",querymaxx(u,v));
}
scanf("%s",opt);
}
return 0;
}