点分治
Posted Winniechen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了点分治相关的知识,希望对你有一定的参考价值。
点分治:
通常用来处理树上路径信息,选出部分,把部分的全部处理更新答案,用部分与部分之间的联系求出整体。
一般,我们选择重心作为分治对象。
对两个不属于统一部份的进行合并的时候,我们通常需要预处理出到分治中心的信息,再对这种信息进行合并。
点分治很多题的关键在于容斥原理,容斥原理通常能够排除掉很多不正确的
BZOJ1468 Tree
存一下每个点到分治中心的距离,双指针扫一下求出所有满足题意的路径,再减去子树中自己本身的
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> using namespace std; #define N 400005 #define ll long long int head[N],cnt,siz[N],maxx[N],rot,num,ans,n; ll dep[N],K; struct node { int to,next,val; }e[N<<1]; bool vis[N]; void add(int x,int y,int z) { e[++cnt].to=y; e[cnt].next=head[x]; e[cnt].val=z; head[x]=cnt; return ; } void get_root(int x,int from) { maxx[x]=0,siz[x]=1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&(!vis[to1])) { get_root(to1,x); siz[x]+=siz[to1]; maxx[x]=max(maxx[x],siz[to1]); } } maxx[x]=max(maxx[x],num-siz[x]); if(maxx[rot]>maxx[x])rot=x; } ll a[N]; int cnt1; void get_dep(int x,int from) { a[++cnt1]=dep[x]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&(!vis[to1])) { dep[to1]=dep[x]+e[i].val; get_dep(to1,x); } } } int calc(int x) { int sum=0; cnt1=0; get_dep(x,0); sort(a+1,a+cnt1+1); int h=1,t=cnt1; while(h<t) { if(a[t]+a[h]>K)t--; else { sum+=t-h; h++; } } return sum; } void dfs(int x) { dep[x]=0,vis[x]=1;ans+=calc(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { dep[to1]=e[i].val; ans-=calc(to1); num=siz[to1]; rot=0; get_root(to1,x); dfs(rot); } } } char s[2]; int main() { memset(head,-1,sizeof(head)); scanf("%d",&n); for(int i=1;i<n;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } num=n; scanf("%d",&K); maxx[0]=1<<30; get_root(1,0); dfs(rot); printf("%d\n",ans); return 0; }
BZOJ2152 聪聪可可
求恰好是3的倍数,那么我们存一下到根的距离为%3==0,%3==1,%3==2的方案数,最后合计一下更新答案就好了,注意细节。
附上代码:
#include <cstdio> #include <cmath> #include <iostream> #include <queue> #include <cstdlib> #include <algorithm> #include <cstring> using namespace std; #define N 20005 #define ll long long int head[N],cnt,siz[N],sn,rot,mx[N],n; ll ans,f[3],dep[N]; bool vis[N]; struct node { int to,next,val; }e[N<<1]; void add(int x,int y,int z) { e[++cnt].to=y; e[cnt].next=head[x]; e[cnt].val=z; head[x]=cnt; return ; } void getroot(int x,int from) { siz[x]=1,mx[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&(!vis[to1])) { getroot(to1,x); siz[x]+=siz[to1]; mx[x]=max(mx[x],siz[to1]); } } mx[x]=max(mx[x],sn-siz[x]); if(mx[x]<mx[rot])rot=x; return ; } void get_dep(int x,int from) { f[dep[x]]++; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&(!vis[to1])) { dep[to1]=(dep[x]+e[i].val)%3; get_dep(to1,x); } } } ll calc(int x) { f[0]=0,f[1]=0,f[2]=0; get_dep(x,0); return (f[0]*f[0])+(2*f[1]*f[2]); } void dfs(int x) { dep[x]=0;vis[x]=1;ans+=calc(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { dep[to1]=e[i].val; ans-=calc(to1); sn=siz[to1]; rot=0; getroot(to1,0); dfs(rot); } } } ll gcd(ll a,ll b) { if(!b)return a; return gcd(b,a%b); } int main() { memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); scanf("%d",&n); for(int i=1;i<n;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z%3); add(y,x,z%3); } mx[0]=1<<30; sn=n; getroot(1,0); dfs(rot); ll t=gcd(ans,n*n); printf("%lld/%lld\n",ans/t,n*n/t); return 0; }
BZOJ3697: 采药人的路径
其实这个题挺有趣的,我们可以考虑,如果没有限制2的话,和上道题差不多,那么现在我们考虑加上条件二,什么样的路径可以更新答案呢?我们可以考虑,更新的时候加上一个限制,就是判断是否可以满足有一个中间点,容斥原理减掉多余的,什么时候存在中间点呢?我们就是这条路径的长度包涵在已经有长度的区间中,至于为什么正确,因为容斥的时候会将不正确的排除掉,剩下的就是同样的东西了,关键就在第二问。
附上代码:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstring> #include <cstdlib> using namespace std; #define N 100005 #define ll long long int siz[N],mx[N],head[N],cnt,tot,sn,rot,vis[N],n,len; struct node { int to,next,val; }e[N<<1]; ll ans,f[N][2],g[N][2]; void add(int x,int y,int z) { e[cnt].to=y; e[cnt].val=z; e[cnt].next=head[x]; head[x]=cnt++; } void get_root(int x,int from) { siz[x]=1;mx[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { get_root(to1,x); siz[x]+=siz[to1]; mx[x]=max(mx[x],siz[to1]); } } mx[x]=max(mx[x],sn-siz[x]); if(mx[rot]>mx[x])rot=x; } void calc(int x,int from,int now,int cnt) { if(now==0) { if(cnt>=2)ans++; cnt++; } for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { calc(to1,x,now+e[i].val,cnt); } } } void get_dep(int x,int from,int now,int l,int r) { if(l<=now&&now<=r) { if(now>=0)f[now][1]++; else g[-now][1]++; }else { if(now>=0)f[now][0]++; else g[-now][0]++; } l=min(l,now);r=max(r,now);len=max(max(r,-l),len); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]&&to1!=from) { get_dep(to1,x,now+e[i].val,l,r); } } } void dfs(int x) { vis[x]=1;calc(x,0,0,0); get_dep(x,0,0,1,-1),ans+=f[0][1]*(f[0][1]-1)/2;f[0][0]=f[0][1]=0; for(int i=1;i<=len;i++)ans+=f[i][0]*g[i][1]+g[i][0]*f[i][1]+f[i][1]*g[i][1],f[i][0]=f[i][1]=g[i][1]=g[i][0]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { len=0;get_dep(to1,0,e[i].val,0,0);ans-=f[0][1]*(f[0][1]-1)/2;f[0][1]=f[0][0]=0; for(int i=0;i<=len;i++)ans-=f[i][0]*g[i][1]+g[i][0]*f[i][1]+f[i][1]*g[i][1],f[i][0]=f[i][1]=g[i][1]=g[i][0]=0; sn=siz[to1]; rot=0;get_root(to1,0); dfs(rot); } } } int main() { memset(head,-1,sizeof(head)); scanf("%d",&n); for(int i=1;i<n;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z*2-1); add(y,x,z*2-1); } sn=n;mx[0]=n; get_root(1,0); dfs(rot); printf("%lld\n",ans); return 0; }
BZOJ1316: 树上的询问
同样的原理,get_dep之后处理长度,就是需要离线一下,每次处理出所有答案的结果,关键在离线,在线的话时间复杂度会有毛病。
附上代码:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstring> #include <cstdlib> using namespace std; #define N 10005 #define ll long long int siz[N],mx[N],head[N],cnt,tot,sn,rot,vis[N],n,len,a[105],dep[N],d[N],Q,p[N]; struct node { int to,next,val; }e[N<<1]; bool ans[105],b[1000005]; void add(int x,int y,int z) { e[cnt].to=y; e[cnt].val=z; e[cnt].next=head[x]; head[x]=cnt++; } void get_root(int x,int from) { siz[x]=1;mx[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { get_root(to1,x); siz[x]+=siz[to1]; mx[x]=max(mx[x],siz[to1]); } } mx[x]=max(mx[x],sn-siz[x]); if(mx[rot]>mx[x])rot=x; } void get_dep(int x,int from) { d[++tot]=dep[x]; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { dep[to1]=dep[x]+e[i].val; get_dep(to1,x); } } } void calc(int x) { b[0]=1;int cnt1=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { tot=0;dep[to1]=e[i].val; get_dep(to1,0); for(int j=1;j<=tot;j++) { for(int k=1;k<=Q;k++) { if(a[k]>=d[j]&&b[a[k]-d[j]]) { ans[k]=1; } } } for(int j=1;j<=tot;j++) { if(d[j]<=1000000) { p[++cnt1]=d[j]; b[d[j]]=1; } } } } for(int i=1;i<=cnt1;i++)b[p[i]]=0; } void dfs(int x) { vis[x]=1; get_dep(x,0); calc(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { sn=siz[to1]; rot=0;get_root(to1,0); dfs(rot); } } } int main() { memset(head,-1,sizeof(head)); scanf("%d%d",&n,&Q); for(int i=1;i<n;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z); } for(int i=1;i<=Q;i++) { scanf("%d",&a[i]); if(!a[i])ans[i]=1; } sn=n;mx[0]=1<<30; get_root(1,0); dfs(rot); for(int i=1;i<=Q;i++) { if(ans[i])puts("Yes"); else puts("No"); } return 0; }
BZOJ4016: [FJOI2014]最短路径树问题
这个题,我已经无力吐槽了,这个题的难点在读题和求最短路树,Dijkstra找出最短路图,在最短路图上跑以最小字典序的树,就可以了。
calc的时候树形DP求长度为K的最长链,状态f[i][j]表示节点i,选择j个节点的最长链,随便写写,当然,这个东西不用容斥原理了,其实点分治是可以优化一些树形DP的,例如这道题和下一道题。
附上代码:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstring> #include <cstdlib> #include <set> #include <vector> using namespace std; #define N 30005 #define ll long long int ans,ans1,siz[N],mx[N],head1[N],head[N],dep[N],cnt,cnt1,tot,sn,rot,vis[N],n,len,dis[N],K,m,num[N],f[N],g[N],num1[N]; struct node { int to,next,val; }E[N<<2],e[N<<1]; void add1(int x,int y,int z) { E[cnt1].to=y; E[cnt1].val=z; E[cnt1].next=head1[x]; head1[x]=cnt1++; return ; } void add(int x,int y,int z) { e[cnt].to=y; e[cnt].val=z; e[cnt].next=head[x]; head[x]=cnt++; return ; } priority_queue<pair<int,int> >q; vector<int> v[N]; void dijkstra() { memset(dis,0x3f,sizeof(dis)); dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()) { int x=q.top().second;q.pop(); if(vis[x])continue; vis[x]=1; for(int i=head1[x];i!=-1;i=E[i].next) { int to1=E[i].to; if(dis[x]==dis[to1]+E[i].val) { v[to1].push_back(i^1); } } for(int i=head1[x];i!=-1;i=E[i].next) { int to1=E[i].to; if(dis[to1]>dis[x]+E[i].val) { dis[to1]=dis[x]+E[i].val; q.push(make_pair(-dis[to1],to1)); } } } } bool cmp(int a,int b) { return E[a].to<E[b].to; } bool used[N]; void build(int x) { sort(v[x].begin(),v[x].end(),cmp); for(int i=0;i<v[x].size();i++) { int to1=E[v[x][i]].to; if(!used[to1]) { used[to1]=1; add(x,to1,E[v[x][i]].val); add(to1,x,E[v[x][i]].val); build(to1); } } } /* void prin(int x,int from) { printf("%d\n",x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from) { prin(to1,x); } } }*/ void get_root(int x,int from) { siz[x]=1;mx[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { get_root(to1,x); siz[x]+=siz[to1]; mx[x]=max(mx[x],siz[to1]); } } mx[x]=max(mx[x],sn-siz[x]); if(mx[rot]>mx[x])rot=x; } void get_dep(int x,int from) { for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { dep[to1]=dep[x]+1;dis[to1]=dis[x]+e[i].val;len=max(dep[to1],len); if(dis[to1]>f[dep[to1]])f[dep[to1]]=dis[to1],num[dep[to1]]=1; else if(dis[to1]==f[dep[to1]])num[dep[to1]]++; get_dep(to1,x); } } } void calc(int x) { int mlen=0; g[0]=0,num1[0]=1; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { num[1]=len=dep[to1]=1;dis[to1]=f[1]=e[i].val;get_dep(to1,0); for(int j=1;j<=len&&j<=K;j++) { if(ans<f[j]+g[K-j])ans=f[j]+g[K-j],ans1=num[j]*num1[K-j]; else if(ans==f[j]+g[K-j])ans1+=num[j]*num1[K-j]; } for(int j=1;j<=len&&j<=K;j++) { if(g[j]<f[j])g[j]=f[j],num1[j]=num[j]; else if(g[j]==f[j])num1[j]+=num[j]; } for(int j=1;j<=len&&j<=K;j++)f[j]=-1<<30,num[j]=0; mlen=max(mlen,len); } } for(int i=1;i<=mlen&&i<=K;i++) { g[i]=-1<<30;num1[i]=0; } } void dfs(int x) { vis[x]=1; calc(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { sn=siz[to1]; rot=0;get_root(to1,0); dfs(rot); } } } int main() { memset(head1,-1,sizeof(head1)); memset(head,-1,sizeof(head)); scanf("%d%d%d",&n,&m,&K);K--; for(int i=1;i<=m;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); add1(x,y,z); add1(y,x,z); } dijkstra(); memset(vis,0,sizeof(vis));cnt=0;used[1]=1;build(1);//prin(1,0); memset(dis,0,sizeof(dis));memset(f,0xc0,sizeof(f)); memset(g,0xc0,sizeof(g));g[0]=0,num1[0]=1; sn=n;mx[0]=1<<30; get_root(1,0); dfs(rot); printf("%d %d\n",ans,ans1); return 0; }
BZOJ4182: Shopping
这道题非常的神啊,裸上树形DP是nm^2的,如果用二进制拆分的话,还要多一个log,不存在可过性,剪一剪枝能不能过不太清楚,不过看数据的样子,过的可能性不是很大,卡评测什么的可不好。
可以看出,这又是一道有关路径信息的问题,这种情况下,我们可以想点分治能不能做。
因为点分治其实是将树分成许多部分来处理,那么我们只需要知道每次的分治中心的最大值来更新答案就可以了。
证明:如果一部分不经过分治中心,那么就一定在同一个子树中,那么最后一定会得到一个联通块满足这一部分经过它的分治中心。
那么,我们就可以将问题转化为,给你一棵树,要求你求出经过根的一个联通块在满足题意取得最大值。这个就更加好想了一点,树形背包转化为dfs序+背包。状态不变,还是f[i][j]表示dfs序上第i个点,在i-n中选择了j个的最大值。
我们考虑如果选择一个节点,那么必定会选择这个节点的父节点,那么,我们考虑,f[i][j]=f[i+1][j-c[i]]+w[i];那么如果我们不选择这个节点,就必定不会选择这个节点子树中的任何节点,也就是,f[i][j]=max(f[i][j],f[i+siz[i]][j]);
附上代码:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstring> #include <cstdlib> #include <set> #include <vector> using namespace std; #define N 505 #define ll long long struct node { int to,next; }e[N<<1]; int head[N],cnt,siz[N],vis[N],mx[N],f[N][4005],ans,w[N],c[N],d[N],n,m,rot,sn; void add(int x,int y) { e[cnt].to=y; e[cnt].next=head[x]; head[x]=cnt++; return ; } void get_root(int x,int from) { siz[x]=1,mx[x]=0; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { get_root(to1,x); siz[x]+=siz[to1]; mx[x]=max(mx[x],siz[to1]); } } mx[x]=max(mx[x],sn-siz[x]); if(mx[rot]>mx[x])rot=x; } int idx[N],tot,last[N]; void get(int x,int from) { idx[++tot]=x; for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(to1!=from&&!vis[to1]) { get(to1,x); } } last[x]=tot; } void calc(int x) { tot=0;get(x,0); for(int i=1;i<=tot+1;i++) { for(int j=0;j<=m;j++) { f[i][j]=0; } } for(int i=tot;i;i--) { int t=d[idx[i]]-1; for(int j=m;j>=c[idx[i]];j--) { f[i][j]=f[i+1][j-c[idx[i]]]+w[idx[i]]; } for(int j=1;j<=t;t-=j,j<<=1) { for(int k=m;k>=j*c[idx[i]];k--) { f[i][k]=max(f[i][k],f[i][k-j*c[idx[i]]]+w[idx[i]]*j); } } if(t) { for(int j=m;j>=t*c[idx[i]];j--) { f[i][j]=max(f[i][j],f[i][j-t*c[idx[i]]]+w[idx[i]]*t); } } for(int j=m;j>=0;j--)f[i][j]=max(f[i][j],f[last[idx[i]]+1][j]); } ans=max(ans,f[1][m]); } void dfs(int x) { vis[x]=1; calc(x); for(int i=head[x];i!=-1;i=e[i].next) { int to1=e[i].to; if(!vis[to1]) { sn=siz[to1]; rot=0; get_root(to1,0); dfs(rot); } } } void init() { memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); cnt=0; } int main() { int T; scanf("%d",&T); while(T--) { init(); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&w[i]); } for(int i=1;i<=n;i++) { scanf("%d",&c[i]); } for(int i=1;i<=n;i++) { scanf("%d",&d[i]); } for(int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); add(y,x); } mx[0]=1<<30;rot=0;ans=0;sn=n; get_root(1,0); dfs(rot); printf("%d\n",ans); } return 0; }
先更新到这里...学完CDQ分治再更新
以上是关于点分治的主要内容,如果未能解决你的问题,请参考以下文章