点分治:
通常用来处理树上路径信息,选出部分,把部分的全部处理更新答案,用部分与部分之间的联系求出整体。
一般,我们选择重心作为分治对象。
对两个不属于统一部份的进行合并的时候,我们通常需要预处理出到分治中心的信息,再对这种信息进行合并。
点分治很多题的关键在于容斥原理,容斥原理通常能够排除掉很多不正确的
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分治再更新