笔记-Kruskal重构树(二)
U13笔记
例1:KK3177
题面
题目描述
有一棵 \(n\) 个节点的树,每条边都有一个正整数权值,\(q\) 个问题,询问从 \(v\) 号节点出发,只通过权值不少于 \(k\) 的边,最多能到达多少个除自己之外的节点。
输入格式 recommendation.in
输入第一行包含正整数 \(n\) 和 \(q\) 。
接着 \(n-1\) 行,每行三个正整数 \(u,v,w\) 代表有一条端点为 \(u\) 和 \(v\) 的权值为 \(w\) 的边。
接着 \(q\) 行,每行两个正整数 \(k\) 和 \(v\) 。
输出格式
对于每个问询输出一行,每行一个正整数。
输入样例
4 3
1 2 3
2 3 2
2 4 4
1 2
4 1
3 1
输出样例
数据范围
\(n,q \le 100000\)
\(w,k \le 1000000000\)
算法分析
多个离线问询
树上 \(u\) 号节点出发通过边权不小于 \(k\) 的边能够连通到几个其他点
对所有边:按照边权从大到小排序
对所有问询:按照边权参数从大到小排序
双游标:一个游标扫描所有边,一个游标扫描所有问题
主导游标:扫描问题
辅助游标:扫描边
Code
int main(){
int n,nQ;
cin>>n>>nQ;
int nE=n-1;
for(int iE=1;iE<=n-1;iE++)
cin>>e[iE].u>>e[iE].v>>e[iE].w;
sort(e+1,e+1+nE,cmpE);
for(int iQ=1;iQ<=nQ;iQ++){
cin>>q[iQ].k>>q[iQ].v;
q[iQ].id=iQ;
}
sort(q+1,q+1+nQ,cmpQ);
for(int u=1;u<=n;u++){
fa[u]=u;
sz[u]=1;
//sz[u] 表示 u 号节点所在的连通块的节点总数
}
int iE=1;
for(int iQ=1;iQ<=nQ;iQ++){
for(;iE<=nE;iE++){
if(e[iE].w<q[iQ].k)break;
int ru=root(e[iE].u);
int rv=root(e[iE].v);
if(ru==rv)continue;
fa[ru]=rv;
sz[rv]+=sz[ru];
}
ans[q[iQ].id]=sz[root(q[iQ].v)]-1;
}
}
加强版题目
如果题目要求强制在线:
暴力
可撤销并查集
算法分析
依然可以预处理完成准备工作
Kruskal 重构树 梳理出边权的大小等级
先建重构树,回答问题时从出发点往上跳到一个最高的合法的祖先,答案就是这个祖先的子树里面的叶子个数-1
Code
void dfs(int u,int fa){
d[u]=d[fa]+1;
p[u][0]=fa;
for(int i=1;i<=L;i++)
p[u][i]=p[p[u][i-1]][i-1];
for(int i=0;i<(int)son[u].size();i++)
dfs(son[u][i],u);
}
//倍增找最高合法祖先
int query(int k,int v){
if(val[p[v][0]]<k)return 0;
v=p[v][0];
for(int i=L;i>=0;i--){
if(d[v]<=(1<<i))continue;
if(val[p[v][i]]<k)continue;
v=p[v][i];
}
return nLeafs[v]-1;
}
int main(){
cin>>n>>nQ;
int nE=n-1;
for(int iE=1;iE<=nE;iE++)
cin>>e[iE].u>>e[iE].v>>e[iE].w;
for(int u=1;u<=n*2;u++)fa[u]=u;
for(int u=1;u<=n;u++)nLeafs[u]=1;
//nLeafs[u] 表示 u 号节点所在子树中有几个叶节点
int nV=n;
sort(e+1,e+1+nE,cmpE);
for(int iE=1;iE<=nE;iE++){
int ru=root(e[iE].u);
int rv=root(e[iE].v);
if(ru==rv)continue;
nV++;
fa[ru]=fa[rv]=nV;
nLeafs[nV]=nLeafs[ru]+nLeafs[rv];
val[nV]=e[iE].w;
son[nV].push_back(ru);
son[nV].push_back(rv);
}
L=log(n)/log(2)+1;
dfs(nV,0);
int k,v;
for(int iQ=1;iQ<=nQ;iQ++){
cin>>k>>v;
cout<<query(k,v)<<endl;
}
}
例2:KK3178(NOI2018归程)
算法分析
预计算:最短路标记
Dijkstra
dst[u] 表示 1 号节点到 \(u\) 号节点的最短距离
重构 MST:梳理海拔层次
Kruskal
minD[u] 表示 \(u\) 号子树内叶节点(原始节点)的最短路标记的最小值
易错点:
多测清空
三张图并存:
- 原始海拔高度图 边集数组
- 原始距离图 链式前向星
- Kruskal重构树 儿子列表
Code
int query(int k,int v){
if(height[p[v][0]]<=k)return minD[v];
v=p[v][0];
for(int i=L;i>=0;i--){
if(d[v]<=(1<<i))continue;
}
}
void solve(){
scanf("%d %d",&n,&m);
nE=0;
for(int u=1;u<=n;u++)hd[u]=0;
for(int u=1;u<=n*2;u++)son[u].clear();
for(int i=1;i<=m;i++){
scanf("%d %d %d %d",&e[i].u,&e[i].v,&e[i].l,&e[i].h);
add(e[i].u,e[i].v,e[i].l);
add(e[i].v,e[i].u,e[i].l);
}
Dijkstra();
for(int u=1;u<=2*n;u++)fa[u]=u;
for(int u=1;u<=n;u++){
sz[u]=1;
minD[u]=dst[u];
}
int nV=n;
sort(e+1,e+1+m,cmpE);
for(int i=1;i<=m;i++){
int ru=root(e[i].u);
int rv=root(e[i].v);
if(ru==rv)continue;
nV++;
fa[ru]=fa[rv]=nV;
height[nV]=e[i].h;
minD[nV]=min(minD[ru],minD[rv]);
son[nV].push_back(ru);
son[nV].push_back(rv);
}
L=log(n)/log(2)+1;
dfs(nV,0);
int k,s;
int v,p;
int ans=0;
scanf("%d %d %d",&nQ,&k,&s);
for(int iQ=1;iQ<=nQ;iQ++){
scanf("%d %d",&v,&p);
v=(v+k*ans-1)%n+1;
p=(p+k*ans)%(s+1);
ans=query(p,v);
printf("%d\n",ans);
}
}
int main(){
freopen("return.in","r",stdin);
freopen("return.out","w",stdout);
int T;
scanf("%d",&T);
for(int t=1;t<=T;t++)
solve();
}

浙公网安备 33010602011771号