点分治与dsu学习笔记
前言
又想起来在西安学了但回来就忘了学过了的东西。
到了这里我似乎只剩多项式没写了吧。
点分治
【模板】点分治 1
我们将树上路径分为两类,一类为经过根节点的,一类为不经过根节点的。
同时,我们定义 \(dis_x\) 表示从 \(x\) 出发到根节点的路径长。
那么对于第一类路径,有两点 \((x,y)\) 间距离为:\(dis_x+dis_y\) 。
对于第二类路径,将两路径的 LCA 为根,直接转化成第一类路径。
这就是点分治思想,简单而言,将一棵树划分为许多子树,然后依次求解。
复杂度分析:对于每一次求 \(dis\) ,是 \(O(n)\) 的,所以复杂度是 \(O(T\times n)\) ,\(T\) 为递归层数。
我们并不希望 \(T\) 过于劣,所以我们定的根需要是重心,这样的复杂度是 \(O(n\log n)\) 的,否则最劣为 \(O(n^2)\) 。
code:
#include<iostream>
using namespace std;
const int N=1e5+50;
const int M=2e7+50;
struct Edge{
int to,nxt,w;
}e[2*N];
int head[N],tot;
void add(int u,int v,int w){
e[++tot].to=v;
e[tot].nxt=head[u];
e[tot].w=w;
head[u]=tot;
return ;
}
int rt,maxp[N],siz[N],dis[N],rem[N];
int vis[N],cnt,judge[M],q[N],res[N];
int query[1010];
int sum,ans;
int n,m;
void grt(int u,int pa){
siz[u]=1;maxp[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==pa || vis[v]) continue;
grt(v,u);
siz[u]+=siz[v];
maxp[u]=max(maxp[u],siz[v]);
}
maxp[u]=max(maxp[u],sum-siz[u]);
if(maxp[u]<maxp[rt]) rt=u;
return ;
}
void gdis(int u,int fa){
rem[++cnt]=dis[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa || vis[v]) continue;
dis[v]=dis[u]+e[i].w;
gdis(v,u);
}
return ;
}
void calc(int u){
int p=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v]) continue;
cnt=0;
dis[v]=e[i].w;
gdis(v,u);
for(int j=cnt;j>=0;j--){
for(int k=1;k<=m;k++){
if(query[k]>=rem[j]){
res[k]|=judge[query[k]-rem[j]];
}
}
}
for(int j=cnt;j>=0;j--){
q[++p]=rem[j],judge[rem[j]]=1;
}
}
for(int i=1;i<=p;i++){
judge[q[i]]=0;
}
return ;
}
void solve(int u){
vis[u]=judge[0]=1;
calc(u);
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v]) continue;
sum=siz[v],maxp[rt=0]=M;
grt(v,0);
solve(rt);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
for(int i=1;i<=m;i++){
cin>>query[i];
}
maxp[rt]=sum=n;
grt(1,0);
solve(rt);
for(int i=1;i<=m;i++){
if(res[i]) cout<<"AYE"<<'\n';
else cout<<"NAY"<<'\n';
}
return 0;
}
树上启发式合并(dsu on tree)
只能处理离线问题,是个离线算法。
只有对子树的询问。
Lomsat gelral
板子题
题目大意:
有一棵以 \(1\) 号结点为根的有根树。
每个结点都有一个颜色,颜色是以编号表示的,\(i\) 号结点的颜色编号为 \(c_i\) 如果一种颜色在以 \(x\) 为根的子树内出现次数最多,称其在以 \(x\) 为根的子树中占主导地位。
显然,同一子树中可能有多种颜色占主导地位。
你的任务是对于每一个 \(i∈[1,n]\),求出以 \(i\) 为根的子树中,占主导地位的颜色的编号和。
\(N\le 10^5,c_i\le n\)
可以发现,每个节点的答案其实由节点的子树决定,所以可以将子树的贡献算完后直接给父亲。
但是,父亲的答案并不由一棵子树单独决定,所以每次计算完后还需要把贡献撤销。
但是,最后一棵计算的子树不用将贡献撤销了。
令最后一棵子树是重儿子,而后的算法流程如下:
- 递归所有轻儿子,并消除递归计算时产生的贡献。
- 递归重儿子,不消除递归计算时产生的贡献。
- 统计轻儿子对答案的贡献。
- 更新答案。
- 消除轻儿子对答案的贡献。
code:
#include<iostream>
#define int long long
using namespace std;
const int N=1e5+50;
int n,c[N];
struct Edge{
int to,nxt;
}e[2*N];
int head[N],tot;
void add(int u,int v){
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
return ;
}
int siz[N],hson[N];
int cnt[N];
int mx,res,ans[N],son;
void dfs1(int u,int f){
siz[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[hson[u]]<siz[v]){
hson[u]=v;
}
}
return ;
}
void calc(int u,int f,int val){
cnt[c[u]]+=val;
if(cnt[c[u]]>mx) mx=cnt[c[u]],res=c[u];
else if(cnt[c[u]]==mx) res+=c[u];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==f || v==son) continue;
calc(v,u,val);
}
return ;
}
void dfs(int u,int f,bool keep){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==f || v==hson[u]) continue;
dfs(v,u,0);
}
if(hson[u]) dfs(hson[u],u,1),son=hson[u];
calc(u,f,1);
son=0;
ans[u]=res;
if(!keep) calc(u,f,-1),res=0,mx=0;
return ;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>c[i];
}
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs1(1,0);
dfs(1,0,1);
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
return 0;
}

浙公网安备 33010602011771号