机房最后一个学习动态点分治的人
前置知识:点分治(雾
动态点分治也叫点分树,就是把点分治时候每个重心组成的树拿出来用于动态维护一些东西
建点分树的过程就是点分治过程,同时记下每层重心的父亲(也就是上一层重心),一般是提前建出点分树,然后每次修改和询问时暴力跳树,因为点分树的树高是
l
o
g
n
logn
logn级别的,所以总复杂度是
n
l
o
g
n
nlogn
nlogn
(当然有时候需要一些数据结构可能会成为
n
l
o
g
2
n
nlog^2n
nlog2n
例题:
bzoj3730: 震波
同样的套路,先建出点分树并且记下每个点的
d
e
p
dep
dep(就是层数),和它上几层的父亲(它上面每一层的重心),还有它到这些父亲的距离。
用两个树状数组
f
,
g
f,g
f,g分别求离
x
x
x距离为
i
i
i的点的权值和,离
f
a
x
fa_x
fax距离为
i
i
i的点的权值和,修改的时候就暴力跳
d
e
p
dep
dep,修改
f
,
g
f,g
f,g,查询的时候也是,可以知道每个重心到它的距离,用
k
k
k减去就是重心到所求点的距离设为
x
x
x,用点分树的这个子树中到根距离不超过
x
x
x的权值加起来,因为会算重,所以还要减掉离儿子的父亲距离不超过
x
x
x的权值。(如果不明白可以画画图!
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
#define LL long long
using namespace std;
inline int rd(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}
int n,m,val[N],cnt,head[N],nxt[N<<1],to[N<<1];
int fa[N][20],dis[N][20],rt,mx,siz[N],dep[N],ans;
vector<int> f[N],g[N];
bool vis[N];
inline void add(int x,int y){
to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}
void getroot(int u,int fat,int tot){
siz[u]=1; int maxx=0;
for(int i=head[u],v;i;i=nxt[i])
if(!vis[v=to[i]] && v!=fat){
getroot(v,u,tot);
siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
}
maxx=max(maxx,tot-siz[u]);
if(maxx<mx) mx=maxx,rt=u;
}
void getdis(int u,int fat,int t,int d){
for(int i=head[u],v;i;i=nxt[i])
if(!vis[v=to[i]] && v!=fat){
fa[v][++dep[v]]=t,dis[v][dep[v]]=d;
getdis(v,u,t,d+1);
}
}
void Divide(int root,int tot){
vis[root]=1; getdis(root,0,root,1);
f[root].resize(tot+1); g[root].resize(tot+1);
for(int i=head[root],v;i;i=nxt[i])
if(!vis[v=to[i]]){
int now=siz[v]>siz[root]?tot-siz[root]:siz[v];
mx=n+1; getroot(v,root,now);
Divide(rt,now);
}
}
inline int query1(int x,int k){int sum=val[x],lim=f[x].size()-1; k=min(k,lim); for(;k;k-=k&-k) sum+=f[x][k];return sum;}
inline int query2(int x,int k){int sum=0, lim=g[x].size()-1; k=min(k,lim); for(;k;k-=k&-k) sum+=g[x][k];return sum;}
inline void change(int x,int v){
int lim=f[x].size()-1,d=dis[x][dep[x]];
for(int i=d;i<=lim&&i;i+=i&-i) g[x][i]+=v;
for(int i=dep[x];i;i--){
d=dis[x][i]; lim=f[fa[x][i]].size()-1;
for(int j=d;j<=lim;j+=j&-j) f[fa[x][i]][j]+=v;
d=dis[x][i-1];
for(int j=d;j<=lim&&j;j+=j&-j) g[fa[x][i]][j]+=v;
}
}
inline int ask(int x,int k){
int ret=query1(x,k);
for(int i=dep[x];i;i--)
if(dis[x][i]<=k)
ret+=query1(fa[x][i],k-dis[x][i])-query2(fa[x][i+1],k-dis[x][i]);
return ret;
}
int main(){
n=rd(); m=rd(); int x,y,z;
for(int i=1;i<=n;i++) val[i]=rd();
for(int i=1;i<n;i++) x=rd(),y=rd(),add(x,y);
mx=n+1; getroot(1,0,n); Divide(rt,n);
for(int i=1;i<=n;i++) fa[i][dep[i]+1]=i;
for(int i=1;i<=n;i++) change(i,val[i]);
while(m--){
x=rd(),y=rd()^ans,z=rd()^ans;
if(!x) printf("%d\n",ans=ask(y,z));
else change(y,z-val[y]),val[y]=z;
}
return 0;
}
bzoj4372: 烁烁的游戏
和上一题一模一样的思路,只不过相当于修改和询问换了一下,这样大概要单点修改查询后缀和,把树状数组的操作全反一下就行了,注意本身的值要手动修改因为树状数组下标不能为
0
0
0
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
using namespace std;
inline int rd(){
int x=0,f=1;char c=getchar();
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return x<y?x:y;}
int n,m,cnt,head[N],nxt[N<<1],to[N<<1],val[N];
int rt,mx,siz[N],dep[N],fa[N][20],dis[N][20];
vector<int> f[N],g[N];
bool vis[N];
char s[5];
inline void add(int x,int y){
to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}
void getroot(int u,int fat,int tot){
siz[u]=1; int maxx=0;
for(int i=head[u],v;i;i=nxt[i])
if(!vis[v=to[i]] && v!=fat){
getroot(v,u,tot);
siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
}
maxx=max(maxx,tot-siz[u]);
if(maxx<mx) mx=maxx,rt=u;
}
void getdis(int u,int fat,int t,int d){
for(int i=head[u],v;i;i=nxt[i])
if(!vis[v=to[i]] && v!=fat){
fa[v][++dep[v]]=t,dis[v][dep[v]]=d;
getdis(v,u,t,d+1);
}
}
void Divide(int root,int tot){
vis[root]=1; getdis(root,0,root,1);
f[root].resize(tot+1); g[root].resize(tot+1);
for(int i=head[root],v;i;i=nxt[i])
if(!vis[v=to[i]]){
int now=siz[v]>siz[root]?tot-siz[root]:siz[v];
mx=n+1; getroot(v,root,now);
Divide(rt,now);
}
}
inline int query1(int x,int k){int sum=0,lim=f[x].size()-1;for(;k&&k<=lim;k+=k&-k)sum+=f[x][k];return sum;}
inline int query2(int x,int k){int sum=0,lim=g[x].size()-1;for(;k&&k<=lim;k+=k&-k)sum+=g[x][k];return sum;}
inline void change(int x,int k,int w){
val[x]+=w; if(!k) return;
int d=k,lim=f[x].size()-1; d=min(d,lim);
for(int i=d;i;i-=i&-i) f[x][i]+=w;
for(int i=dep[x];i;i--)
if(dis[x][i]<=k){
val[fa[x][i]]+=w;
d=k-dis[x][i],lim=f[fa[x][i]].size()-1; d=min(d,lim);
for(int j=d;j;j-=j&-j) f[fa[x][i]][j]+=w;
lim=g[fa[x][i+1]].size()-1; d=min(d,lim);
for(int j=d;j;j-=j&-j) g[fa[x][i+1]][j]+=w;
}
}
inline int ask(int x){
int d,res=val[x];
for(int i=dep[x];i;i--){
d=dis[x][i];
res+=query1(fa[x][i],d)-query2(fa[x][i+1],d);
}
return res;
}
int main(){
n=rd(); m=rd(); int x,y,z;
for(int i=1;i<n;i++) x=rd(),y=rd(),add(x,y);
mx=n+1; getroot(1,0,n); Divide(rt,n);
for(int i=1;i<=n;i++) fa[i][dep[i]+1]=i;
while(m--){
scanf("%s",s);
if(s[0]=='Q') x=rd(),printf("%d\n",ask(x));
else x=rd(),y=rd(),z=rd(),change(x,y,z);
}
return 0;
}
窝发现那个记录每个节点往上的点分树上的节点的方法非常好用诶!
这个题就是发现假设
u
u
u为根,如果从
u
−
>
v
u->v
u−>v,则代价增量为
(
s
u
m
u
−
2
∗
s
u
m
v
)
∗
d
i
s
(
u
,
v
)
(sum_u-2*sum_v)*dis(u,v)
(sumu−2∗sumv)∗dis(u,v),
s
u
m
u
sum_u
sumu就是以
u
u
u为根的子树中
d
d
d的和,那就是只有
2
∗
s
u
m
v
>
s
u
m
u
2*sum_v>sum_u
2∗sumv>sumu的时候才能从
u
u
u到
v
v
v,这样的
v
v
v一定只有一个,所以就可以从当前点分树的根开始往下找,统计答案就是再记录
s
1
,
s
2
s1,s2
s1,s2分别表示这个点子树的答案和这个点子树到它父亲的答案,然后每次暴力跳树高求答案就好了,如果求出这个
v
v
v的答案比
u
u
u更优,就直接跳到
v
v
v所在点分树的根,继续求就好了。
复杂度
O
(
m
l
o
g
2
n
)
O(mlog^2n)
O(mlog2n)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define N 100005
#define LL long long
using namespace std;
template<class T>inline void rd(T &x){
x=0; short f=1; char c=getchar();
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
x*=f;
}
inline int max(int x,int y){return x>y?x:y;}
int n,m,cnt,head[N],nxt[N<<1],to[N<<1],w[N<<1];
int siz[N],dep[N],mx,rt,fat[N][20],Rt;
LL sum[N],s1[N],s2[N],dis[N][20];
bool vis[N];
vector<int> son[N],gson[N];
inline void add(int x,int y,int z){
to[++cnt]=y,nxt[cnt]=head[x],w[cnt]=z,head[x]=cnt;
to[++cnt]=x,nxt[cnt]=head[y],w[cnt]=z,head[y]=cnt;
}
void getroot(int u,int fa,int tot){
siz[u]=1; int maxx=0;
for(int i=head[u],v;i;i=nxt[i])
if((v=to[i])!=fa && !vis[v]){
getroot(v,u,tot);
siz[u]+=siz[v];
maxx=max(maxx,siz[v]);
}
maxx=max(maxx,tot-siz[u]);
if(maxx<mx) mx=maxx,rt=u;
}
void getdis(int u,int fa,int t,LL d){
fat[u][++dep[u]]=t,dis[u][dep[u]]=d;
for(int i=head[u],v;i;i=nxt[i])
if((v=to[i])!=fa && !vis[v])
getdis(v,u,t,d+w[i]);
}
void Divide(int root,int tot){
vis[root]=1; getdis(root,0,root,0);
for(int i=head[root],v;i;i=nxt[i])
if(!vis[v=to[i]]){
int num=siz[v]<siz[root]?siz[v]:tot-siz[root];
mx=n+1; getroot(v,root,num);
son[root].push_back(v); gson[root].push_back(rt);
Divide(rt,num);
}
}
inline void change(int v,int val){
for(int i=dep[v],u;i;i--){
u=fat[v][i]; sum[u]+=val;
s1[u]+=1LL*dis[v][i]*val; s2[u]+=1LL*dis[v][i-1]*val;
} return;
}
inline LL calc(int u){
LL ret=s1[u]; int x,y;
for(int i=dep[u]-1;i>0;i--){
x=fat[u][i],y=fat[u][i+1];
ret+=s1[x]-s2[y]+1LL*(sum[x]-sum[y])*dis[u][i];
} return ret;
}
inline LL ask(int u){
LL ans=calc(u);
for(int i=0,v;i<son[u].size();i++){
v=son[u][i];
if(calc(v)<ans) return ask(gson[u][i]);
} return ans;
}
int main(){
rd(n); rd(m); int x,y,z;
for(int i=1;i<n;i++){rd(x),rd(y),rd(z);add(x,y,z);}
mx=n+1; getroot(1,0,n); Rt=rt; Divide(rt,n);
while(m--){
rd(x),rd(y);
change(x,y);
printf("%lld\n",ask(Rt));
}
return 0;
}
bzoj1095: [ZJOI2007]Hide 捉迷藏
一道毒题,看了三天都没有一个好的解决方法只好去看题解。。。
维护三种堆:
1.
f
[
u
]
f[u]
f[u]表示
u
u
u为根的子树中黑点到
G
f
a
u
Gfa_u
Gfau的距离的堆
2.
g
[
u
]
g[u]
g[u]表示点分树中
u
u
u的子节点的
f
f
f堆顶
3.
a
n
s
ans
ans表示所有的
g
g
g中堆顶和次堆顶的和
那么答案就是
a
n
s
.
t
o
p
(
)
ans.top()
ans.top()
考虑动态维护这三种堆,当把一个房间开关灯的时候,这个节点
u
u
u的
f
[
u
]
f[u]
f[u]会改变,进而会改变
u
u
u在点分树上所有的父亲的
g
g
g,进而改变
a
n
s
ans
ans
所以每次先把
a
n
s
ans
ans中
g
g
g的部分删掉,然后把
g
g
g中
f
[
u
]
f[u]
f[u]的堆顶删掉,修改
f
[
u
]
f[u]
f[u],再修改
g
g
g和
a
n
s
ans
ans
这里的堆用的是一个插入堆一个删除堆的那种操作
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define N 100005
#define LL long long
#define inf 0x3f3f3f3f
using namespace std;
template<class T>inline void rd(T &x){
x=0; short f=1; char c=getchar();
while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
x*=f;
}
int n,m,cnt,head[N],nxt[N<<1],to[N<<1],siz[N],val[N];
int rt,mx,dep[N],fat[N][20],dis[N][20];
bool vis[N];
struct Heap{
priority_queue<int> a,b;
inline void push(int v){a.push(v);}
inline void del(int v){b.push(v);}
inline bool empty(){while(!a.empty() && !b.empty() && a.top()==b.top()) a.pop(),b.pop(); return a.empty();}
inline int size(){return a.size()-b.size();}
inline void pop(){if(!empty()) a.pop();}
inline int top(){if(!empty()) return a.top();return -inf;}
inline int setop(){int tmp=top();pop();int ret=top();push(tmp);return ret;}
}f[N],g[N],ans;
inline void add(int x,int y){
to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;
to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt;
}
inline void insert(Heap &s){
if(s.size()>=2) ans.push(s.top()+s.setop());
}
inline void erase(Heap &s){
if(s.size()>=2) ans.del(s.top()+s.setop());
}
void getroot(int u,int fa,int tot){
siz[u]=1; int maxx=0;
for(int i=head[u],v;i;i=nxt[i])
if((v=to[i])!=fa && !vis[v]){
getroot(v,u,tot);
siz[u]+=siz[v]; maxx=max(maxx,siz[v]);
}
maxx=max(maxx,tot-siz[u]);
if(maxx<mx) mx=maxx,rt=u;
}
void getdis(int u,int fa,int t,int d,Heap &s){
s.push(d);
fat[u][++dep[u]]=t,dis[u][dep[u]]=d;
for(int i=head[u],v;i;i=nxt[i])
if((v=to[i])!=fa && !vis[v])
getdis(v,u,t,d+1,s);
}
void Divide(int root,int tot){
vis[root]=1; g[root].push(0);
for(int i=head[root],v;i;i=nxt[i])
if(!vis[v=to[i]]){
int now=siz[v]<siz[root]?siz[v]:tot-siz[root];
mx=n+1; getroot(v,root,now);
getdis(v,root,root,1,f[rt]);
g[root].push(f[rt].top());
Divide(rt,now);
}
insert(g[root]);
}
inline void turn_off(int u){
erase(g[u]); g[u].push(0); insert(g[u]);
for(int i=dep[u]+1;i>1;i--){
int x=fat[u][i],y=fat[u][i-1];
erase(g[y]);
if(f[x].size()) g[y].del(f[x].top());
f[x].push(dis[u][i-1]);
if(f[x].size()) g[y].push(f[x].top());
insert(g[y]);
}
}
inline void turn_on(int u){
erase(g[u]); g[u].del(0); insert(g[u]);
for(int i=dep[u]+1;i>1;i--){
int x=fat[u][i],y=fat[u][i-1];
erase(g[y]);
if(f[x].size()) g[y].del(f[x].top());
f[x].del(dis[u][i-1]);
if(f[x].size()) g[y].push(f[x].top());
insert(g[y]);
}
}
inline void change(int u){
if(val[u]) val[u]=0,turn_off(u);
else val[u]=1,turn_on(u);
}
int main(){
rd(n); int x,y; char op[10];
for(int i=1;i<n;i++) rd(x),rd(y),add(x,y);
rd(m); mx=n+1; getroot(1,0,n); Divide(rt,n);
for(int i=1;i<=n;i++) fat[i][dep[i]+1]=i;
while(m--){
scanf("%s",op);
if(op[0]=='C') rd(x),change(x);
else printf("%d\n",(x=ans.top())>0?x:-1);
}
return 0;
}