【知识点】点分治
- 点分治简介
点分治是树分治的一种,是处理大规模树上路径问题强力武器。
- 步骤:
- 找到当前树的重心,以重心为根节点
- 处理经过当前根节点的路径
- 删除根节点
- 对于生成的每棵子树,重复以上步骤
找重心:O(n)
void findrt(int u,int fa){ //sz表示子树的大小,son表示点的最大子树的大小 sz[u]=1,son[u]=0; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(vis[v]||v==fa) continue; findrt(v,u); sz[u]+=sz[v]; son[u]=max(son[u],sz[v]); } //size表示整棵树的大小 //因为这是一棵无根树,所以包括它的父亲在内的那一片也应该算作它的子树 son[u]=max(son[u],size-sz[u]); if(son[u]<mx) mx=son[u],rt=u;//最大子树大小最小 }
分治相关:
void divide(int u){ ans+=solve(u,0);//把当前节点的答案加上去 vis[u]=1;//把节点标记,防止陷入死循环 for(int i=head[u];i;i=Next[i]){ //分别处理每一棵子树 int v=ver[i]; if(vis[v]) continue; ans-=solve(v,edge[i]);//容斥原理,下面说 mx=inf,rt=0; size=sz[v]>sz[u]?totsz-sz[u]:sz[v];//这里应该这样写才是对的 //把所有信息更新,递归进子树找重心,并继续分治 findrt(v,0); divide(rt); } }
好啦,贴了一些自己独立敲不出来的代码,试图完全的理解,失败了。。。
所以就直接上板子!!!
板子也找了半天!对于我这种没有完全理解透的,只能尝试找一个可以自己改成适用各种题目的板子:
- 这个是一个大神用树状数组桶排序做的:(有一说一,我应该不咋会用,我可能就是放这里%%)
题目链接:https://www.luogu.com.cn/problem/P4178
#include<iostream> #include<cstring> #include<cstdio> using namespace std; inline int rd(){ int ret=0,f=1;char c; while(c=getchar(),!isdigit(c))f=c=='-'?-1:1; while(isdigit(c))ret=ret*10+c-'0',c=getchar(); return ret*f; } const int MAXN=262144; const int INF=1<<29; int n,m; struct Edge{ int next,to,w; }e[MAXN<<1]; int ecnt,head[MAXN]; inline void add(int x,int y,int w){ e[++ecnt].next = head[x]; e[ecnt].to = y; e[ecnt].w = w; head[x] = ecnt; } int t[MAXN]; void update(int x,int val){ for(int i=x;i<=m;i+=i&-i)t[i]+=val; } int query(int x){ if(x==0) return 0; int ret=0; for(int i=x;i;i-=i&-i)ret+=t[i]; return ret; } bool vis[MAXN]; int siz[MAXN]; void getsiz(int x,int pre){ siz[x]=1; for(int i=head[x];i;i=e[i].next){ int v=e[i].to; if(vis[v]||v==pre) continue; getsiz(v,x); siz[x]+=siz[v]; } } int mn=INF,root; void getroot(int x,int pre,int tot){ int mx=0; for(int i=head[x];i;i=e[i].next){ int v=e[i].to; if(vis[v]||v==pre) continue; mx=max(mx,siz[v]); getroot(v,x,tot); } mx=max(mx,tot-siz[x]); if(mx<mn) mn=mx,root=x; } int s[MAXN],sav[MAXN]; void dfs(int x,int pre,int dis){ if(dis>m) return; s[++s[0]]=dis;sav[++sav[0]]=dis; for(int i=head[x];i;i=e[i].next){ int v=e[i].to; if(vis[v]||v==pre) continue; dfs(v,x,dis+e[i].w); } } int ans; void dac(int x){//Divide and Conquer :) sav[0]=0;mn=n; getsiz(x,-1); getroot(x,-1,siz[x]); int u=root;vis[u]=1; for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]) continue; s[0]=0;dfs(v,u,e[i].w); for(int j=s[0];j>=1;j--){ if(s[j]>m) continue; ans+=query(m-s[j]); } for(int j=s[0];j>=1;j--){ if(s[j]>m) continue; update(s[j],1); ans++; } } for(int i=sav[0];i>=1;i--){ if(sav[i]>m) continue; update(sav[i],-1); } for(int i=head[u];i;i=e[i].next){ int v=e[i].to; if(vis[v]) continue; dac(v); } } int main(){ n=rd(); int x,y,w; for(int i=1;i<n;i++){ x=rd();y=rd();w=rd(); add(x,y,w);add(y,x,w); } m=rd(); dac(1); cout<<ans; return 0; }
题目链接:https://www.luogu.com.cn/problemnew/solution/P3806
#include<iostream> #include<vector> #include<algorithm> #include<queue> #include<cstring> #include<cstdio> using namespace std; int read() { int f=1,x=0; char ss=getchar(); while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();} while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();} return f*x; } const int inf=10000000; const int maxn=100010; int n,m; struct node{int v,dis,nxt;}E[maxn<<1]; int tot,head[maxn]; int maxp[maxn],size[maxn],dis[maxn],rem[maxn]; int vis[maxn],test[inf],judge[inf],q[maxn]; int query[1010]; int sum,rt; int ans; void add(int u,int v,int dis) { E[++tot].nxt=head[u]; E[tot].v=v; E[tot].dis=dis; head[u]=tot; } void getrt(int u,int pa) { size[u]=1; maxp[u]=0; for(int i=head[u];i;i=E[i].nxt) { int v=E[i].v; if(v==pa||vis[v]) continue; getrt(v,u); size[u]+=size[v]; maxp[u]=max(maxp[u],size[v]); } maxp[u]=max(maxp[u],sum-size[u]); if(maxp[u]<maxp[rt]) rt=u; } void getdis(int u,int fa) { rem[++rem[0]]=dis[u]; for(int i=head[u];i;i=E[i].nxt) { int v=E[i].v; if(v==fa||vis[v])continue; dis[v]=dis[u]+E[i].dis; getdis(v,u); } } void calc(int u) { int p=0; for(int i=head[u];i;i=E[i].nxt) { int v=E[i].v; if(vis[v])continue; rem[0]=0; dis[v]=E[i].dis; getdis(v,u);//处理u的每个子树的dis for(int j=rem[0];j;--j)//遍历当前子树的dis for(int k=1;k<=m;++k)//遍历每个询问 if(query[k]>=rem[j]) test[k]|=judge[query[k]-rem[j]]; //如果query[k]-rem[j]d的路径存在就标记第k个询问 for(int j=rem[0];j;--j)//保存出现过的dis于judge q[++p]=rem[j],judge[rem[j]]=1; } for(int i=1;i<=p;++i)//处理完这个子树就清空judge judge[q[i]]=0;//特别注意一定不要用memset,会T } void solve(int u) { //judge[i]表示到根距离为i的路径是否存在 vis[u]=judge[0]=1; calc(u);//处理以u为根的子树 for(int i=head[u];i;i=E[i].nxt)//对每个子树进行分治 { int v=E[i].v; if(vis[v])continue; sum=size[v]; maxp[rt=0]=inf; getrt(v,0); solve(rt);//在子树中找重心并递归处理 } } int main() { n=read();m=read(); for(int i=1;i<n;++i) { int u=read(),v=read(),dis=read(); add(u,v,dis);add(v,u,dis); } for(int i=1;i<=m;++i) query[i]=read();//先记录每个询问以离线处理 maxp[rt]=sum=n;//第一次先找整棵树的重心 getrt(1,0); solve(rt);//对树进行点分治 for(int i=1;i<=m;++i) { if(test[i]) printf("AYE\n"); else printf("NAY\n"); } return 0; }
上面这种是容斥思想加两指针扫;
有一些如果不是问方案数而是最值问题就不适合
未写完待续...