好像原来都没学会
点分治
每次找到重心,然后在子树里面统计信息
最后容斥掉同一个子树里面的答案,这里注意要加(或减) 边的信息
点分树
如果带修的树上信息统计就需要这个东西
其实还是个容斥,就是这里维护一个数据结构就行了
以 「震波」 一题为例
首先建出来点分树,然后对于每个点维护两个数据结构 (S_1,S_2) ,这题目中可以考虑是 (BIT/SGT)
答案的构成分为在点分树子树里面的和不在的,那么求的时候跳点分树每次求就行了
这样的话会重复一部分,那么容斥掉
具体而言,维护到 (x) 的距离有一个前缀和,维护到 (fa[x]) 的距离有一个前缀和
对于查询操作,每次暴力跳父亲,查询在子树里面的答案和当前点 (S_1) 减掉子树里的 (S_2)
对于修改操作,还是暴力跳父亲,每次修改即可
需要动态开点和卡常数
Code
#include<bits/stdc++.h>
using namespace std;
#define reg register
namespace yspm{
inline int read(){
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=1e5+10,M=N*60;
struct node{int to,nxt;}e[N<<1];
int f[N],fa[N],mx,head[N],cnt,v[N],sum,rt,n,sz[N],TS,ans,anc;
bool vis[N];
inline void add(int u,int v){
e[++cnt].to=v; e[cnt].nxt=head[u];
return head[u]=cnt,void();
}
struct LCA{
int fa[N][20],dep[N];
inline void dfs(int x,int fat){
dep[x]=dep[fat]+1; fa[x][0]=fat;
for(reg int i=1;(1<<i)<=n;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
for(reg int i=head[x];i;i=e[i].nxt){
int t=e[i].to; if(t==fat) continue;
dfs(t,x);
} return ;
}
inline int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(reg int i=19;~i;--i) if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(reg int i=19;~i;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
}D;//求树上的距离
inline void findrt(int x,int fat){
sz[x]=1; f[x]=0;
for(reg int i=head[x];i;i=e[i].nxt){
int t=e[i].to; if(vis[t]||t==fat) continue;
findrt(t,x); sz[x]+=sz[t]; f[x]=max(f[x],sz[t]);
} f[x]=max(f[x],sum-sz[x]); if(f[x]<mx) mx=f[x],rt=x; return ;
}
inline void updsz(int x,int fat){
sz[x]=1;
for(reg int i=head[x];i;i=e[i].nxt){
int t=e[i].to; if(vis[t]||t==fat) continue;
updsz(t,x); sz[x]+=sz[t];
} return ;
}
vector<int> g[N];
inline void build(int x){
vis[x]=1;
for(reg int i=head[x];i;i=e[i].nxt){
int t=e[i].to; if(vis[t]) continue;
updsz(x,0); mx=2e9+10; sum=sz[t]; findrt(t,0); fa[rt]=x; g[x].push_back(rt);
build(rt);
} return ;
}
int rot[N][2];
struct SGT{
int ls[M],rs[M],sum[M],tot;
inline void push_up(int x){sum[x]=sum[ls[x]]+sum[rs[x]]; return;}
inline void upd(int &p,int l,int r,int pos,int val){
if(!p) p=++tot;
if(l==r) return sum[p]+=val,void();
int mid=(l+r)>>1;
if(pos<=mid) upd(ls[p],l,mid,pos,val);
else upd(rs[p],mid+1,r,pos,val);
return push_up(p);
}
inline int query(int p,int l,int r,int st,int ed){
if(!p) return 0;
if(st<=l&&r<=ed) return sum[p];
int mid=(l+r)>>1,res=0;
if(st<=mid) res+=query(ls[p],l,mid,st,ed);
if(ed>mid) res+=query(rs[p],mid+1,r,st,ed);
return res;
}
}T;
inline void upd(int pos,int val){
for(reg int tp=pos;tp;tp=fa[tp]){
T.upd(rot[tp][0],0,sz[tp],D.dis(tp,pos),val);
if(fa[tp]) T.upd(rot[tp][1],0,sz[fa[tp]],D.dis(fa[tp],pos),val);
} return ;
}
inline int calc(int pos,int dis){
int res=0;
for(reg int t1=pos,t2=0;t1;t2=t1,t1=fa[t1]){
int d=D.dis(pos,t1);
if(d>dis) continue;
res+=T.query(rot[t1][0],0,sz[t1],0,dis-d);
if(t2) res-=T.query(rot[t2][1],0,sz[t1],0,dis-d);
}return res;
}
inline void prework(int x){
sz[x]=1;
for(auto t:g[x]) prework(t),sz[x]+=sz[t];
return ;
}
signed main(){
n=read(); TS=read(); for(reg int i=1;i<=n;++i) v[i]=read();
for(reg int i=1,u,vv;i<n;++i) u=read(),vv=read(),add(u,vv),add(vv,u);
mx=2e9+10; sum=n; findrt(1,0);
anc=rt;
build(rt); D.dfs(1,0); prework(anc);
for(reg int i=1;i<=n;++i) upd(i,v[i]);
for(reg int x,y;TS--;){
if(read()){
x=read()^ans; y=read()^ans;
upd(x,-v[x]); upd(x,v[x]=y);
}else{
x=read()^ans,y=read()^ans;
printf("%d
",ans=calc(x,y));
}
}
return 0;
}
}
signed main(){return yspm::main();}
例题
开店
好象是非常板子的题目诶
这个区间的维护显然是可以套个数据结构
那么用线段树下标就似乎点分子树里面 年龄 到当前点的距离和
那么每次查询就暴力在点分子树上面跳父亲,然后查询父亲节点的信息,这里统计一个数量和一个距离和
考虑容斥,直接数量减掉就完事了,距离和加上那个端点之间的距离乘数目即可
然后这就过去了三个小时,空间显然是不太行
其实自己知道好像可以用map改一下,空间能优化不少
接着换成vector就过掉了
幻想乡战略游戏
我怕是个弱智
想了好长时间的其他做法,却又犯了没有推式子的老毛病
其中有 (O(nQ)) 的换根 (dp) 和 维护子树内向最优解的增量差这样子
正解如下:
先设 (n_x) 为 (x) 子树里面的点权和,(S_x) 表示答案
若以 (x) 为根,其一个子节点的 (y) 满足 (S_x-S_y<0)
那么有 ((n_x-2 imes n_y) imes dis_{x,y})
上面是颓的式子,剩下都是想到的
用点分树维护答案是显然的,那么唯一的问题就是找到一个满足不存在子节点的点权和的二倍大于自己的这样一个点
直接在点分树上面找就完事了
这题目上来挂了一发的原因是自己在计算点权和的时候父亲的点权应该是直接总的减子树,当时写假了
捉迷藏
可以对每个点维护一个 (multiset),存进去每个点到其子树里面所有点的距离
如果设 (s_1[x]) 为最大的点权,(s_2[x]) 为次大的点权,这里默认如果没有黑点就是 (-1)
那么有一个点的答案就是 (s_1[x]+s_2[x])
把所有点的答案扔进一个 (multiset)
如果修改的话就是删掉一个点或者加入一个点
套上点分治就可以保证空间复杂度有效
但是不难发现这个东西没有容斥,那么数据结构里面再维护个来源
不太好写,但是好像复杂度没啥问题
不过发现了种线段树维护区间直径的方法,就完了
大概一开始挂了几发是因为维护 (dfn) 和点的关系维护错了
模式字符串
首先核心思路是把两个串拼起来
那么每次点分治统计经过当前的点的答案就行了
每个分治重心暴力扫子树,这样做总的被扫的次数是 (sum dep=Theta(nlog n)),复杂度貌似挺对的
总感觉数据是水了,因为 (1e6) 和并不小的常数不太能跑过呀
剩下紫荆花之恋,大概会附在虚树笔记的后面