[基本操作]点分治
众所周知
点分治是基本操作
——Destinies_Gdx
点分治是处理树上路径问题的很好的方法,它可以把树变成一棵平衡的二叉树来使很多看起来是 $O(n^2)$ 的操作变成 $O(nlogn)$ 的
poj1741 Tree
给你一棵树,求长度不超过 k 的简单路径数量
sol:
点分治
每次找一个重心,计算 depth
然后用双指针法计算答案
然后递归,减去每个儿子对父亲贡献的答案
每次双指针会计算 x 的子树内部所有答案,但其实要减去两个点都在 x 的一个子树 u 的子树里的情况
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<cstdlib> #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } void fre() { freopen("mydata.in","r",stdin); freopen("mydata.out","w",stdout); } const int maxn = 3e5 + 10; int n,k,root; LL ans; int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt; inline void add(int u,int v,int w) { to[++cnt] = v; nx[cnt] = first[u]; first[u] = cnt; val[cnt] = w; } inline void ins(int u,int v,int w){add(u,v,w);add(v,u,w);} int size[maxn],f[maxn],vis[maxn],sig; int dis[maxn],stk[maxn],top; inline void getroot(int x,int fa) { size[x] = 1;f[x] = 0; for(int i=first[x];i;i=nx[i]) { if(to[i] == fa || vis[to[i]])continue; getroot(to[i],x); size[x] += size[to[i]]; f[x] = max(f[x],size[to[i]]); } f[x] = max(f[x],sig - size[x]); if(f[x] < f[root])root = x; } inline void getdep(int x,int fa) { stk[++top] = dis[x]; for(int i=first[x];i;i=nx[i]) { if(to[i] == fa || vis[to[i]])continue; dis[to[i]] = dis[x] + val[i]; getdep(to[i],x); } } inline LL calc(int x,int len) { dis[x] = len; top = 0; getdep(x,0); sort(stk + 1,stk + top + 1); LL res = 0; int l = 1,r = top; while(l <= r) { while(stk[r] + stk[l] > k && r > l)r--; res += r - l; l++; } return res; } inline void work(int x) { vis[x] = 1; ans += calc(x,0); for(int i=first[x];i;i=nx[i]) { if(vis[to[i]])continue; ans -= calc(to[i],val[i]); root = 0,sig = size[to[i]]; getroot(to[i],x);work(root); } } int main() { #ifdef Ez3real fre(); #endif while(1) { n = read(),k = read(); cnt = 0;ans = 0; memset(first,0,sizeof(first)); memset(vis,0,sizeof(vis)); if(n == 0 && k == 0)return 0; for(int i=2;i<=n;i++) { int u = read(),v = read(),w = read(); ins(u,v,w); } sig = n,root = 0;f[0] = 2147483233; getroot(1,0); work(root); printf("%lld\n",ans); } }
bzoj2152 聪聪可可
给一棵树,每次随机两个点,求这两个点的简单路径上边权和膜 3 余 0 的概率
sol:
就是找有多少路径膜 3 余 0
所以可以记一个数组 $cnt[0/1/2]$ 表示 x 子树里膜 3 余 $0/1/2$ 的路径数
一个子树的贡献就是 $cnt[0] \times cnt[0] + cnt[1] \times cnt[2] \times 2$
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<cstdlib> #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } void fre() { freopen("mydata.in","r",stdin); freopen("mydata.out","w",stdout); } const int maxn = 3e5 + 10; int n,k,root; LL ans,cc; int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt; inline void add(int u,int v,int w) { to[++cnt] = v; nx[cnt] = first[u]; first[u] = cnt; val[cnt] = w; } inline void ins(int u,int v,int w){add(u,v,w);add(v,u,w);} int size[maxn],f[maxn],vis[maxn],sig; int dis[maxn],cntu[maxn]; inline void getroot(int x,int fa) { size[x] = 1;f[x] = 0; for(int i=first[x];i;i=nx[i]) { if(to[i] == fa || vis[to[i]])continue; getroot(to[i],x); size[x] += size[to[i]]; f[x] = max(f[x],size[to[i]]); } f[x] = max(f[x],sig - size[x]); if(f[x] < f[root])root = x; } inline void getdep(int x,int fa) { cntu[dis[x] % 3]++; for(int i=first[x];i;i=nx[i]) { if(vis[to[i]] || to[i] == fa)continue; dis[to[i]] = dis[x] + val[i]; getdep(to[i],x); } } inline LL calc(int x,int len) { dis[x] = len; cntu[0] = cntu[1] = cntu[2] = 0; getdep(x,0); return 1LL * (cntu[0] * cntu[0] + cntu[1] * cntu[2] * 2); } inline void work(int x) { vis[x] = 1; ans += calc(x,0); for(int i=first[x];i;i=nx[i]) { if(vis[to[i]])continue; ans -= calc(to[i],val[i]); root = 0,sig = size[to[i]]; getroot(to[i],x);work(root); } } int main() { #ifdef Ez3real fre(); #endif n = read();cc = n * n;//,k = read(); //cnt = 0;ans = 0; //memset(first,0,sizeof(first)); //memset(vis,0,sizeof(vis)); //if(n == 0 && k == 0)return 0; for(int i=2;i<=n;i++) { int u = read(),v = read(),w = read() % 3; ins(u,v,w); } sig = n,root = 0;f[0] = 2147483233; getroot(1,0); work(root); printf("%lld/%lld\n",ans / __gcd(ans,cc),cc / __gcd(ans,cc)); }
bzoj1095 捉迷藏
给一棵树,每个点黑色或者白色,q 次操作,每次改变一个点的颜色,询问两个最远黑点的距离
sol:
从 Destinies_Gdx 那里学习了一波动态点分治
大概就是先建出点分治的数据结构,然后每个点维护数据结构支持修改和查询
对于这道题我们要维护 3 个可删除的堆分别表示
1.子树内到该分治节点的最大距离
2.子树内到该分治节点的分治树上父节点的最大距离
3.每个点的最大答案
我们把每个子树里的最长链传到 2. 里,就保证了每个子树只传上来一条链,不会出现自己走到自己的情况
全局答案就是每个点 2. 的最大值和次大值之和,每次修改的时候暴力爬树高(树高是 $O(logn)$ 的)更新这 3 个堆即可
#include<bits/stdc++.h> #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } const int maxn = 100010; int n,m; int lg[maxn + maxn]; int root,sz,size[maxn],f[maxn],vis[maxn]; vector<int> G[maxn]; int ind[maxn],dfn,reh[maxn],mn[maxn][24]; int col[maxn],dep[maxn]; namespace sp_lca { int size[maxn],fa[maxn],bl[maxn]; inline void dfs1(int x) { size[x] = 1; for(int i=0;i<G[x].size();i++) { int to = G[x][i]; if(to == fa[x])continue; fa[to] = x;dep[to] = dep[x] + 1; dfs1(to);size[x] += size[to]; } } inline void dfs2(int x,int col) { bl[x] = col;int k = 0; for(int i=0;i<G[x].size();i++) { int to = G[x][i]; if(to != fa[x] && size[to] > size[k])k = to; } if(!k)return; dfs2(k,col); for(int i=0;i<G[x].size();i++) { int to = G[x][i]; if(to != fa[x] && to != k)dfs2(to,to); } } } inline int lca(int x,int y) { while(sp_lca::bl[x] != sp_lca::bl[y]) { if(dep[sp_lca::bl[x]] < dep[sp_lca::bl[y]])swap(x,y); x = sp_lca::fa[sp_lca::bl[x]]; }return dep[x] < dep[y] ? x : y; } inline int caldis(int x,int y){return dep[x] + dep[y] - 2 * dep[lca(x,y)];} struct Del_Heap { priority_queue<int> A,B; inline void push(int x){A.push(x);} inline void del(int x){B.push(x);} inline int top() { while(!B.empty() && A.top() == B.top())A.pop(),B.pop(); if(A.size())return A.top(); else return 0; } inline int size(){return A.size() - B.size();} inline int sctop() { if(size() < 2)return 0; int tp = top();del(tp); int ntp = top();push(tp); return ntp; } }A[maxn],B[maxn],C; inline void findroot(int x,int fa) { size[x] = 1;f[x] = 0; for(int i=0;i<G[x].size();i++) { int to = G[x][i]; if(to == fa || vis[to])continue; findroot(to,x); size[x] += size[to]; f[x] = max(f[x],size[to]); } f[x] = max(f[x],sz - size[x]); if(f[x] < f[root])root = x; //cout<<root<<endl; } int par[maxn]; inline void build(int x) { vis[x] = 1; for(int i=0;i<G[x].size();i++) { int to = G[x][i]; if(vis[to])continue; sz = size[x];root = 0;findroot(to,x); par[root] = x;build(root); } } void turn_on(int x,int rt) { if(x == rt) { if(B[x].size() == 2)C.del(B[x].top()); B[x].del(0); } if(!par[x])return; int fa = par[x],ds = caldis(fa,rt); int tp = A[x].top();A[x].del(ds); if(ds == tp) { int lastans = B[fa].top() + B[fa].sctop(); int ww = B[fa].size();B[fa].del(ds); if(A[x].top())B[fa].push(A[x].top()); int nowans = B[fa].top() + B[fa].sctop(); if(nowans < lastans) { if(ww >= 2)C.del(lastans); if(B[fa].size() >= 2)C.push(nowans); } } turn_on(fa,rt); } void turn_off(int x,int rt) { if(x == rt) { B[x].push(0); if(B[x].size() == 2)C.push(B[x].top()); } if(!par[x])return; int fa = par[x],ds = caldis(fa,rt),szb = B[fa].size(); int tp = A[x].top();A[x].push(ds); if(ds > tp) { int lastans = B[fa].top() + B[fa].sctop(); if(tp)B[fa].del(tp);B[fa].push(ds); int nowans = B[fa].top() + B[fa].sctop(); if(nowans > lastans) { if(szb >= 2)C.del(lastans); if(B[fa].size() >= 2)C.push(nowans); } } turn_off(fa,rt); } int main() { n = read(); for(int i=2;i<=n;i++) { int u = read(),v = read(); G[u].push_back(v); G[v].push_back(u); } sp_lca::dfs1(1); sp_lca::dfs2(1,1); sz = n,root = 0;f[0] = 2147483233; findroot(1,0); build(root); for(int i=1;i<=n;i++)A[i].push(0); for(int i=1;i<=n;i++){col[i] = 1;turn_off(i,i);} int nw = n;m = read();char opt[10]; while(m--) { scanf("%s",opt + 1); if(opt[1] == 'G') { if(nw <= 1)printf("%d\n",nw - 1); else printf("%d\n",C.top()); } else { int x = read(); if(col[x] == 1)turn_on(x,x),nw--; else turn_off(x,x),nw++; col[x] ^= 1; } } }
bzoj3924 幻想乡战略游戏
给一棵带边权的树,q 次操作,每次单点点权加,和查询树上带权重心
sol:
感觉动态点分治就是要记录它的信息和它传给它父亲的信息来去重。。。
记 $A_x$ 为子树和
$B_x$ 为子树到它的权值和
$C_x$ 为子树到它父亲的权值和
算一下就可以了
注意 LCA 要 $O(1)$
#include<bits/stdc++.h> #define LL long long const int maxn = 400010; using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } int m,n; int first[maxn],to[maxn],next[maxn],val[maxn],cnt; inline void add(int u,int v,int w) { to[++cnt]=v; val[cnt]=w; next[cnt]=first[u]; first[u]=cnt; } int dep[maxn],dis[maxn],fat[maxn][22],s[maxn]; int id[maxn],ip[maxn],top; void dfs(int u,int fa) { s[++top]=u; if(!id[u])id[u]=top; dep[top]=dep[ip[fa]]+1;ip[u]=top; for(int i=first[u];i;i=next[i]) { int v=to[i]; if(v==fa)continue; dis[v]=dis[u]+val[i]; dfs(v,u);s[++top]=u;dep[top]=dep[ip[fa]+1]; } } void make() { for(int i=1;i<=top;i++) fat[i][0]=i; for(int j=1;j<=18;j++) for(int i=1;i<=top;i++) if(i+(1<<j)-1<=top) { int x=fat[i][j-1],y=fat[i+(1<<j-1)][j-1]; if(dep[fat[i][j-1]]<dep[fat[i+(1<<j-1)][j-1]]) fat[i][j]=fat[i][j-1]; else fat[i][j]=fat[i+(1<<j-1)][j-1]; } } inline int query(int l,int r) { int len=r-l+1,k=0; for(k=0;1<<k+1<=len;k++); if(dep[fat[l][k]]<dep[fat[r-(1<<k)+1][k]])return fat[l][k]; else return fat[r-(1<<k)+1][k]; } inline int lca(int u,int v) { if(id[u]>id[v]) swap(u,v); return s[query(id[u],id[v])]; } inline int caldis(int u,int v) { int LCA=lca(u,v); return dis[u]+dis[v]-2*dis[LCA]; } int rt,sum,f[maxn],size[maxn],vis[maxn]; inline void GetRT(int x,int fa) { size[x]=1;f[x]=0; for(int i=first[x];i;i=next[i]) { if(to[i]==fa || vis[to[i]])continue; GetRT(to[i],x);size[x]+=size[to[i]]; f[x]=max(f[x],size[to[i]]); } f[x]=max(f[x],sum-size[x]); if(f[x]<f[rt])rt=x; } int ret,dv[maxn],par[maxn]; LL ans[maxn],anss[maxn],summ[maxn]; inline void work(int x) { vis[x]=1;summ[x]=ret; for(int i=first[x];i;i=next[i]) { if(vis[to[i]])continue; rt=0,sum=size[to[i]]; GetRT(to[i],0); par[rt]=x;work(rt); } } LL cal(int u) { LL ret=ans[u]; for(int i=u;par[i];i=par[i]) { LL delt=caldis(par[i],u); ret+=(ans[par[i]]-anss[i]); ret+=delt*(summ[par[i]]-summ[i]); } return ret; } LL update(int u,int va) { summ[u]+=va; for(int i=u;par[i];i=par[i]) { LL di=caldis(par[i],u); summ[par[i]]+=va; anss[i]+=va*di; ans[par[i]]+=va*di; } } int last=1; LL query(int u) { LL ka=cal(u); for(int i=first[u];i;i=next[i]) { LL tmp=cal(to[i]); if(tmp < ka)return query(to[i]); } last=u; return ka; } int main() { n = read(),m = read(); for(int i=1;i<n;i++) { int u = read(),v = read(),w = read(); add(u,v,w),add(v,u,w); }top=0,dfs(1,0); make();sum=f[0]=n;GetRT(1,0); work(rt); for(int i=1;i<=m;i++) { int a = read(),b = read(); update(a,b); printf("%lld\n",query(last)); } }
bzoj4012 开店
给一棵有点权和边权的树,每次给一个 $u$ 和一个值域 $[L,R]$,求点权在 $[L,R]$ 的点到 $u$ 的距离和
sol:
对每个分治重心记一下子树内点数,子树到重心的距离和,子树到重心父亲的距离和
对于点权,我们对每个点开一个 vector 存子树内不同颜色的前三种信息前缀和,每次查询二分,暴力爬树高,对每层重心的 vector 二分找到 $[L,R]$ 区间,然后计算 $[1,R] - [1,L-1]$ 的贡献即可
因为很懒所以 $LCA$ 不是 $O(1)$ 的
#include<bits/stdc++.h> #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-') f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } const int maxn = 200010; int n,q,A,yr[maxn]; int first[maxn],to[maxn << 1],nx[maxn << 1],val[maxn << 1],cnt; inline void add(int u,int v,int w) { to[++cnt] = v; nx[cnt] = first[u]; first[u] = cnt; val[cnt] = w; } LL dis[maxn]; int fa[maxn]; namespace LCA { int dep[maxn],bl[maxn],size[maxn]; inline void dfs1(int x) { size[x] = 1; for(int i=first[x];i;i=nx[i]) { if(to[i] == fa[x])continue; fa[to[i]] = x;dep[to[i]] = dep[x] + 1; dis[to[i]] = dis[x] + val[i]; dfs1(to[i]);size[x] += size[to[i]]; } } inline void dfs2(int x,int col) { int k = 0; bl[x] = col; for(int i=first[x];i;i=nx[i]) if(dep[to[i]] > dep[x] && size[to[i]] > size[k])k = to[i]; if(!k)return; dfs2(k,col); for(int i=first[x];i;i=nx[i]) if(dep[to[i]] > dep[x] && to[i] != k)dfs2(to[i],to[i]); } inline int lca(int x,int y) { while(bl[x] != bl[y]) { if(dep[bl[x]] < dep[bl[y]])swap(x,y); x = fa[bl[x]]; } return dep[x] > dep[y] ? y : x; } } struct Node { LL col,sum,sig,cnt; inline bool operator < (const Node &b)const{return col < b.col;} };vector<Node> G[maxn]; inline LL caldis(int x,int y) { // cout<<dis[x] + dis[y] - 2 * dis[LCA::lca(x,y)]<<endl; if(!x || !y)return 0; return dis[x] + dis[y] - 2 * dis[LCA::lca(x,y)]; } int f[maxn],size[maxn],vis[maxn],par[maxn],sig,root; void findroot(int x,int fa) { f[x] = 0,size[x] = 1; for(int i=first[x];i;i=nx[i]) { if(to[i] == fa || vis[to[i]])continue; findroot(to[i],x);size[x] += size[to[i]]; f[x] = max(f[x],size[to[i]]); }f[x] = max(f[x],sig - size[x]); if(f[x] < f[root])root = x; } void add_node(int x,int fa,int rt) { G[rt].push_back((Node){yr[x],caldis(x,rt),(par[rt] ? caldis(x,par[rt]) : 0),1}); for(int i=first[x];i;i=nx[i]) { if(to[i] == fa || vis[to[i]])continue; add_node(to[i],x,rt); } } void build(int x) { vis[x] = 1;add_node(x,0,x); G[x].push_back((Node){-1,0,0,0}); sort(G[x].begin(),G[x].end()); for(int i=1;i<G[x].size();i++) { G[x][i].sum += G[x][i-1].sum; G[x][i].sig += G[x][i-1].sig; G[x][i].cnt += G[x][i-1].cnt; } for(int i=first[x];i;i=nx[i]) { if(vis[to[i]])continue; root = 0;sig = size[to[i]]; findroot(to[i],0);par[root] = x; build(root); } } LL query(int x,int ql,int qr) { LL ans = 0; for(int i=x;i;i=par[i]) { int st,ed; int l = 0,r = G[i].size() - 1; while(l <= r) { int mid = (l + r) >> 1; if(G[i][mid].col <= qr)l = mid + 1; else r = mid - 1; } ed = l - 1; l = 0,r = G[i].size() - 1; while(l <= r) { int mid = (l + r) >> 1; if(G[i][mid].col <= ql - 1)l = mid + 1; else r = mid - 1; } st = l - 1; // cout<<st<<" "<<ed<<endl; ans += (G[i][ed].sum - G[i][st].sum); if(i != x) ans += (G[i][ed].cnt - G[i][st].cnt) * caldis(i,x); if(par[i]) ans -= (G[i][ed].sig - G[i][st].sig) + (G[i][ed].cnt - G[i][st].cnt) * caldis(x,par[i]); }return ans; } int main() { n = read(),q = read(),A = read(); for(int i=1;i<=n;i++)yr[i] = read(); for(int i=2;i<=n;i++) { int u = read(),v = read(),w = read(); add(u,v,w);add(v,u,w); }LCA::dep[1] = 1;LCA::dfs1(1);LCA::dfs2(1,1); sig = n;size[0] = f[0] = 2147483233; findroot(1,0);build(root); LL lastans = 0; while(q--) { int x = read(),a = read(),b = read(); int l=min((a+lastans)%A,(b+lastans)%A); int r=max((a+lastans)%A,(b+lastans)%A); printf("%lld\n",lastans=query(x,l,r)); } }