点分治
点分治
以树的重心为根,对子树内的问题分治求解,时间复杂度可以做到 \(O(n\log n\times F(n))\),其中 \(F(n)\) 是解决经过根的问题所需要的处理。
P3806 模板
给一棵有边权的树,多次询问树上是否存在距离为 \(k\) 的点对。
\(n\le 10^4,m\le 100,k\le 10^7\)
假设现在 \(rt\) 是根,则问题转化为求是否存在不在 \(rt\) 同一子树内的 \(u,v\) 使得 \(dis(rt,u)+dis(rt,v)=k\)。
转化一下式子得 \(dis(rt,u)=k-dis(rt,v)\),我们记 \(is[x]\) 表示是否出现过 \(x\),即对于每一个 \(u\),如果 \(is[k-dis(rt,v)]=1\),则说明有解;对于不在同一子树的限制,直接对于每个子树处理完再加入 \(is\) 就能避免。
时间复杂度 \(O(mn\log n)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e4+3;
const int inf=0x3f3f3f3f;
int n,m;
struct edge{
int v,w;
edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
bool isk[10000003];
bool vis[maxn];
bool ans[maxn];
int maxk,sum;
void add(int u,int v,int w){
e[u].emplace_back(edge(v,w));
e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
siz[u]=1; mxsiz[u]=0;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
centroid(v.v,u);
siz[u]+=siz[v.v];
mxsiz[u]=max(mxsiz[u],siz[v.v]);
}
}
mxsiz[u]=max(mxsiz[u],sum-siz[u]);
if(!rt||mxsiz[u]<mxsiz[rt]){
rt=u;
}
}
void distance(int u,int fa){
d.emplace_back(dis[u]);
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
dis[v.v]=dis[u]+v.w;
distance(v.v,u);
}
}
}
void calc(int u){
for(edge v:e[u]){
if(!vis[v.v]){
dis[v.v]=v.w;
distance(v.v,u);
for(int i:d){
for(int j=1;j<=m;j++){
if(quest[j]>=i){
ans[j]|=isk[quest[j]-i];
}
}
}
while(!d.empty()){
int i=d.back();
if(i<=maxk){
q.push(i);
isk[i]=1;
}
d.pop_back();
}
}
}
while(!q.empty()){
isk[q.front()]=0;
q.pop();
}
}
void dfz(int u,int fa){
isk[0]=1;
vis[u]=1;
calc(u);
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
sum=siz[v.v];
rt=0;
mxsiz[rt]=inf;
centroid(v.v,0);
dfz(rt,0);
}
}
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
}
for(int i=1;i<=m;i++){
cin>>quest[i];
maxk=max(maxk,quest[i]);
}
sum=n;
rt=0;
mxsiz[rt]=inf;
centroid(1,0);
dfz(rt,0);
for(int i=1;i<=m;i++){
if(ans[i]){
cout<<"AYE\n";
}else{
cout<<"NAY\n";
}
}
return 0;
}
P4178 Tree
给一棵有边权的树,求树上距离 \(\le k\) 的点对数。
\(n\le 4\times 10^4,k\le 2\times 10^4\)
做法一:
思路同板子,同理转化一下式子得 \(dis(rt,u)\le k-dis(rt,v)\) 这个偏序问题非常经典,直接树状数组维护即可。
时间复杂度 \(O(n\log n\log k)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=4e4+3;
const int inf=0x3f3f3f3f;
int n,m;
struct edge{
int v,w;
edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
int tree[maxn];
bool vis[maxn];
int ans[maxn];
int maxk,sum;
void add(int u,int v,int w){
e[u].emplace_back(edge(v,w));
e[v].emplace_back(edge(u,w));
}
int lowbit(int x){return x&-x;}
void modify(int x,int c){
x++;
for(;x<=maxk+1;x+=lowbit(x)) tree[x]+=c;
}
int query(int x){
x++;
int ret=0;
for(;x;x-=lowbit(x)) ret+=tree[x];
return ret;
}
void centroid(int u,int fa){
siz[u]=1; mxsiz[u]=0;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
centroid(v.v,u);
siz[u]+=siz[v.v];
mxsiz[u]=max(mxsiz[u],siz[v.v]);
}
}
mxsiz[u]=max(mxsiz[u],sum-siz[u]);
if(!rt||mxsiz[u]<mxsiz[rt]){
rt=u;
}
}
void distance(int u,int fa){
d.emplace_back(dis[u]);
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
dis[v.v]=dis[u]+v.w;
distance(v.v,u);
}
}
}
void dfz(int u,int fa){
modify(0,1);
q.push(0);
vis[u]=1;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
dis[v.v]=v.w;
distance(v.v,u);
for(int i:d){
for(int j=1;j<=m;j++){
if(quest[j]>=i){
ans[j]+=query(quest[j]-i);
}
}
}
while(!d.empty()){
int i=d.back();
if(i<=maxk){
q.push(i);
modify(i,1);
}
d.pop_back();
}
}
}
while(!q.empty()){
modify(q.front(),-1);
q.pop();
}
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
sum=siz[v.v];
rt=0;
mxsiz[rt]=inf;
centroid(v.v,u);
centroid(rt,0);
dfz(rt,0);
}
}
}
signed main(){
cin>>n;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
}
m=1;
for(int i=1;i<=m;i++){
cin>>quest[i];
maxk=max(maxk,quest[i]);
}
sum=n;
rt=0;
mxsiz[rt]=inf;
centroid(1,0);
centroid(rt,0);
dfz(rt,0);
for(int i=1;i<=m;i++){
cout<<ans[i]<<'\n';
}
return 0;
}
做法二:
将所有 \(dis(rt,u)\) 处理出来然后排序,双指针扫一遍 \(dis(rt,L)+dis(rt,R)\le k\) 的,每次更新 \(L\) 的时候答案加上 \(R-L\)。
注意此时并不保证 \(L,R\) 不在同一子树,所有需要容斥,加上 \(rt\) 的总答案,再减掉 \(rt\) 子树的答案即可不重不漏。
时间复杂度 \(O(n\log n)\)。某种情况下比做法一更优。
CF293E Close Vertices
给一棵有边权的树,求树上距离 \(\le k\),边数 \(\le l\) 的点对数。
\(l\le n\le 10^5,k\le 10^9\)
考虑延续上题的做法二思路,先双指针扫 \(dis(rt,L)+dis(rt,R)\le k\) 的答案 \(R-L\),但是这些答案并不都满足边数限制。但是可以发现,边数限制其实也是偏序问题,\(len(rt,u)+len(rt,v)\le l\),把 \(len(rt,v)\) 移到右边去,就用树状数组维护即可,具体就先把 \(len(rt,u)\) 全部加到树状数组上,然后根据 \(L,R\) 的变动,不断减去出界的 \(len\)。
时间复杂度 \(O(n\log^2 n)\)。同样需要容斥。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+3;
const int inf=0x3f3f3f3f3f3f3f3f;
#define int long long
int n,m;
struct edge{
int v,w;
edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
struct node{
int dis,dep;
node(int dis=0,int dep=0): dis(dis),dep(dep){}
bool operator<(const node &o)const{return dis<o.dis;}
}dis[maxn];
int K,L;
vector<node>d;
int tree[maxn];
bool vis[maxn];
int ans;
int sum;
void add(int u,int v,int w){
e[u].emplace_back(edge(v,w));
e[v].emplace_back(edge(u,w));
}
int lowbit(int x){return x&-x;}
void modify(int x,int c){
if(x<0) return;
x++;
for(;x<=L+1;x+=lowbit(x)) tree[x]+=c;
}
int query(int x){
if(x<0) return 0;
x++;
int ret=0;
for(;x;x-=lowbit(x)) ret+=tree[x];
return ret;
}
void centroid(int u,int fa){
siz[u]=1; mxsiz[u]=0;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
centroid(v.v,u);
siz[u]+=siz[v.v];
mxsiz[u]=max(mxsiz[u],siz[v.v]);
}
}
mxsiz[u]=max(mxsiz[u],sum-siz[u]);
if(!rt||mxsiz[u]<mxsiz[rt]){
rt=u;
}
}
void distance(int u,int fa){
d.emplace_back(dis[u]);
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
dis[v.v].dis=dis[u].dis+v.w;
dis[v.v].dep=dis[u].dep+1;
distance(v.v,u);
}
}
}
int calc(int u,int fa,int Dis,int Dep){
dis[u].dis=Dis;
dis[u].dep=Dep;
distance(u,fa);
sort(d.begin(),d.end());
int l=0,r=d.size()-1,ret=0;
for(node i:d) modify(i.dep,1);
while(l<r){
if(d[l].dis+d[r].dis<=K){
modify(d[l].dep,-1);
ret+=query(L-d[l].dep);
l++;
}else{
modify(d[r].dep,-1);
r--;
}
}
modify(d[l].dep,-1);
d.clear();
return ret;
}
void dfz(int u,int fa){
vis[u]=1;
ans+=calc(u,0,0,0);
for(edge v:e[u]){
if(!vis[v.v]){
ans-=calc(v.v,u,v.w,1);
sum=siz[v.v];
rt=0;
mxsiz[rt]=inf;
centroid(v.v,u);
dfz(rt,0);
}
}
}
signed main(){
cin>>n;
cin>>L>>K;
for(int i=1,v,w;i<n;i++){
cin>>v>>w;
add(v,i+1,w);
}
sum=n;
rt=0;
mxsiz[rt]=inf;
centroid(1,0);
// centroid(rt,0);
dfz(rt,0);
cout<<ans;
return 0;
}
其他做法
沿用上题做法一,再套一维数据结构维护,时间复杂度 \(O(n\log^2 n\log k)\) 不太可过。
直接上主席树或 KD-Tree(?)时间复杂度 \(O(n\log^2 n/n\sqrt{n})\)。
P2634 聪聪可可
给一棵有边权的树,求树上距离能被 \(3\) 整除的点对数。
\(n\le 2\times 10^4\)
同样沿用模板的大体思路,就是求 \(3\mid dis(rt,u)+dis(rt,v)\) 的方案数。
在计算距离时取模,将余数为 \(0,1,2\) 的数量 \(c\) 存起来,然后统计答案就是 \((0,0),(1,2),(2,1)\) 分组,答案为 \(c_0^2+2c_1c_2\)。
注意这样统计答案无法避免 \(u,v\) 在 \(rt\) 同一子树的情况,所以使用容斥,加上 \(rt\) 的总答案,再减掉 \(rt\) 子树的答案即可不重不漏。
\(\sum F(n)=n\),所以时间复杂度 \(O(n\log n)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+3;
const int inf=0x3f3f3f3f;
#define int long long
int n,m;
struct edge{
int v,w;
edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
int quest[maxn];
vector<int>d;
queue<int>q;
int isk[3];
bool vis[maxn];
int ans;
int maxk,sum;
void add(int u,int v,int w){
e[u].emplace_back(edge(v,w));
e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
siz[u]=1; mxsiz[u]=0;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
centroid(v.v,u);
siz[u]+=siz[v.v];
mxsiz[u]=max(mxsiz[u],siz[v.v]);
}
}
mxsiz[u]=max(mxsiz[u],sum-siz[u]);
if(!rt||mxsiz[u]<mxsiz[rt]){
rt=u;
}
}
void distance(int u,int fa){
isk[dis[u]]++;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
dis[v.v]=(dis[u]+v.w)%3;
distance(v.v,u);
}
}
}
int calc(int u,int Dis){
isk[0]=isk[1]=isk[2]=0;
dis[u]=Dis%3;
distance(u,0);
return isk[0]*isk[0]+isk[1]*isk[2]*2;
}
void dfz(int u,int fa){
vis[u]=1;
ans+=calc(u,0);
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
ans-=calc(v.v,v.w);
sum=siz[v.v];
rt=0;
mxsiz[rt]=inf;
centroid(v.v,u);
dfz(rt,0);
}
}
}
signed main(){
cin>>n;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w%3);
}
sum=n;
rt=0;
mxsiz[rt]=inf;
centroid(1,0);
dfz(rt,0);
int gcd=__gcd(ans,n*n);
cout<<ans/gcd<<"/"<<n*n/gcd<<'\n';
return 0;
}
P4886 快递员
给出 \(m\) 个点对 \((u_i,v_i)\),求一个点 \(x\),使得 \(dis(x,u_i)+dis(x,v_i)\) 的最大值最小。
\(n,m\le 10^5\)
设当前根为 \(rt\),则有以下结论:
- 若 \(rt\) 在最远点对的路径上,则答案不可能更小;
- 若存在两个最远点对在 \(rt\) 的不同子树内,则答案不可能更小;
- 若以上两条都不满足,则将 \(rt\) 移动到唯一一个包含最远点对的子树内,答案更优。
如上模拟即可。
时间复杂度 \(O(n\log n)\)。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+3;
const int inf=0x3f3f3f3f;
#define int long long
int n,m;
struct edge{
int v,w;
edge(int v=0,int w=0): v(v),w(w){}
};
vector<edge>e[maxn];
int rt;
int siz[maxn],mxsiz[maxn];
int dis[maxn];
struct{
int u,v;
}a[maxn];
vector<int>d;
int sub[maxn];
bool vis[maxn];
int sum;
int ans=inf;
void add(int u,int v,int w){
e[u].emplace_back(edge(v,w));
e[v].emplace_back(edge(u,w));
}
void centroid(int u,int fa){
siz[u]=1; mxsiz[u]=0;
for(edge v:e[u]){
if(v.v!=fa&&!vis[v.v]){
centroid(v.v,u);
siz[u]+=siz[v.v];
mxsiz[u]=max(mxsiz[u],siz[v.v]);
}
}
mxsiz[u]=max(mxsiz[u],sum-siz[u]);
if(!rt||mxsiz[u]<mxsiz[rt]){
rt=u;
}
}
void distance(int u,int fa,int root){
sub[u]=root;
for(edge v:e[u]){
if(v.v!=fa){
dis[v.v]=dis[u]+v.w;
distance(v.v,u,root);
}
}
}
void dfz(int u,int fa){
// cout<<u<<'\n'/;
if(vis[u]){
cout<<ans;
exit(0);
}
vis[u]=1;
dis[u]=0;
for(edge v:e[u]){
dis[v.v]=v.w;
distance(v.v,u,v.v);
}
int lst=0,mx=0;
for(int i=1;i<=m;i++){
if(dis[a[i].u]+dis[a[i].v]>mx){
d.clear();
mx=dis[a[i].u]+dis[a[i].v];
d.emplace_back(i);
}else if(dis[a[i].u]+dis[a[i].v]==mx){
d.emplace_back(i);
}
}
if(mx<ans) ans=mx;
for(int i:d){
if(sub[a[i].u]!=sub[a[i].v]){
cout<<ans;
exit(0);
}
if(!lst) lst=sub[a[i].u];
if(sub[a[i].u]!=lst){
cout<<ans;
exit(0);
}
}
d.clear();
sum=siz[lst];
rt=0;
// mxsiz[rt]=inf;
centroid(lst,0);
dfz(rt,0);
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
}
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v;
}
sum=n;
rt=0;
mxsiz[rt]=inf;
centroid(1,0);
dfz(rt,0);
return 0;
}