kruskal重构树
KruskalKruskalKruskal重构树是一种在图论中用于解决特定问题的数据结构,它基于经典的KruskalKruskalKruskal最小生成树算法构建。它并非直接用于求解最小生成树,而是通过重构原图的边,将边权信息"上移"到新生成的节点上,从而将路径上的边权最值问题转化为树上路径的节点最值问题,并能方便地处理连通性和LCALCALCA 相关的问题
所谓KruskalKruskalKruskal重构树就是把原图重构为一颗二叉树,树上的每一个叶子节点都是原图中的点
重构过程:将所有边权从小到大排序,从小到大选择边,如果两个点不在同一个集合中就将这两个点的祖先连到一个虚点上,这个虚点的点权就等于这条边的边权
神奇的性质:
-
每条边都会新生成一个虚点,因此会增加n−1n-1n−1个虚点
-
原图有几个连通块就会重构出几棵树,若p[i]=ip[i]=ip[i]=i则说明iii是一棵树的树根
-
节点越往上代表边权越大,其连通性也越大,从任何一个点往根上引一条路径,这条路径经过的点的点权单调不降(最大生成树单调不升)
-
任意两点之间的路径的最大边权就是他们lcalcalca的点权,uuu和 vvv的LCALCALCA节点的点权,就是连接uuu和vvv所在连通分量时所用的那条边的权值。
应用场景:
- 最小瓶颈路问题: 求两点间路径上最大边权的最小值。使用边权升序构建的重构树,答案就是LCALCALCA的点权
- 最大瓶颈路问题: 求两点间路径上最小边权的最大值。使用边权降序构建的重构树,答案就是LCALCALCA的点权。
P2245 星际迷航
题意
一张有权无向图,图中可能有若干联通部分,给出qqq个询问,每次询问xxx到yyy的路径中最大边权最小值
代码
cpp
const int N=2e5+10;
struct edge{
int u,v,w;
bool operator < (const edge&t)const
{
return w<t.w;
}
};
int p[N];
vector<int>e[N];
int dep[N];
int fa[N][21];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int father)
{
dep[u]=dep[father]+1;
fa[u][0]=father;
for(int i=1;(1<<i)<=dep[u];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(ed,u);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
}
for(int i=19;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=2*n;i++) p[i]=i;
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e1[i]={a,b,c};
}
sort(all(e1));
vector<int>a(2*n+1);
int cnt=n;
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
a[cnt]=w;
p[x]=cnt;
p[y]=cnt;
}
}
for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);//树根
int q;cin>>q;
while(q--)
{
int x,y;
cin>>x>>y;
if(find(x)!=find(y)) cout<<"impossible"<<endl;
else cout<<a[lca(x,y)]<<endl;
}
}
P1967 货车运输
题目描述
A 国有 nnn 座城市,编号从 111 到 nnn,城市之间有 mmm 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 qqq 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
第一行有两个用一个空格隔开的整数 n,mn,mn,m,表示 A 国有 nnn 座城市和 mmm 条道路。
接下来 mmm 行每行三个整数 x,y,zx, y, zx,y,z,每两个整数之间用一个空格隔开,表示从 xxx 号城市到 yyy 号城市有一条限重为 zzz 的道路。
注意:x≠yx \neq yx=y,两座城市之间可能有多条道路。
接下来一行有一个整数 qqq,表示有 qqq 辆货车需要运货。
接下来 qqq 行,每行两个整数 x,yx,yx,y,之间用一个空格隔开,表示一辆货车需要从 xxx 城市运输货物到 yyy 城市,保证 x≠yx \neq yx=y。
共有 qqq 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 −1-1−1。
思路
等价于求两点之间可能路径的最大瓶颈,重构最大生成树即可
代码
cpp
const int N=2e4+10;
struct edge{
int u,v,w;
bool operator < (const edge&t)const
{
return w>t.w;
}
};
vector<int>e[N];
int p[N];
int dep[N];
int fa[N][20];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int father)
{
dep[u]=dep[father]+1;
fa[u][0]=father;
for(int i=1;(1<<i)<=dep[u];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(ed,u);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
}
for(int i=19;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=2*n;i++) p[i]=i;
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e1[i]={a,b,c};
}
int cnt=n;
sort(all(e1));
vector<int>a(2*n+1);
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
a[cnt]=w;
p[x]=cnt;
p[y]=cnt;
}
}
for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
int q;cin>>q;
while(q--)
{
int x,y;
cin>>x>>y;
if(find(x)!=find(y)) cout<<-1<<endl;
else cout<<a[lca(x,y)]<<endl;
}
}
P9638 youyou的军训
题意
一张nnn点mmm边的无向有权图,有三种操作
1 x
,删去所有边权 ≤x≤x≤x的边,并恢复之前因为该操作而断开的所有边。2 x
,查询节点xxx能到达的包括自己的点的数量。3 y
,将第xxx条边的边权修改为yyy
保证所有边的相对边权排名不变
思路
操作111等价于只能走边权>x>x>x的边,很容易想到瓶颈路,而且保证边的相对排名不会改变,也就是说我们不用多次重构,保证了重构树做法的可行性
具体地,我们按最大生成树来重构,对于修改边权操作我们只需要建立一个边对虚点的映射,每次只更改虚点的点权即可,由于修改操作会在操作1之后统一修改,我们将所有的操作333存起来直到操作111出现后统一修改即可,对于查询,本质上询问的是点xxx只走边权大于limitlimitlimit的边,可以连通的节点数量,我们可以预处理出每个虚点作为根节点时的叶子节点数量(叶节点即可达节点),每次询问倍增跳到最大的合法虚点即可lognlognlogn完成每次查询
代码
cpp
const int N=4e6+10;
struct edge{
int u,v,w,id;
bool operator < (const edge &t) const
{
return w>t.w;
}
};
vector<int>e[N];
int p[N],dep[N];
int fa[N][20];
int siz[N];
int n;
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int father)
{
fa[u][0]=father;
dep[u]=dep[father]+1;
if(u<=n) siz[u]=1;
for(int i=1;(1<<i)<=dep[u];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(ed,u);
siz[u]+=siz[ed];
}
}
void solve()
{
int m,q;
cin>>n>>m>>q;
for(int i=1;i<=2*n;i++) p[i]=i;
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e1[i]={a,b,c,i};
}
sort(all(e1));
vector<int>w1(2*n+1);
vector<int>topoint(2*n+1);//边对虚点的映射
int cnt=n;
for(int i=1;i<=m;i++)
{
auto [u,v,w,id]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
p[x]=cnt;
p[y]=cnt;
w1[cnt]=w;
topoint[id]=cnt;
}
}
for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
vector<PII>change;
int limit=0;
auto query=[&](int x,int limit)
{
for(int i=19;i>=0;i--)
{
if(fa[x][i] && w1[fa[x][i]]>=limit) x=fa[x][i];
}
return siz[x];
};
while(q--)
{
int op;cin>>op;
if(op==1)
{
for(auto [x,y]:change) w1[topoint[x]]=y;
change.clear();
cin>>limit;
}
else if(op==2)
{
int x;cin>>x;
cout<<query(x,limit)<<endl;
}
else
{
int x,y;cin>>x>>y;
if(topoint[x]) change.pb({x,y});
}
}
}
agc_002D
题意
一张nnn点mmm边的无向有权图,每条边的权值是其编号,给出qqq个询问,每次询问从xxx和yyy出发访问不同的节点恰zzz个,使得经过的边编号最大值最小,求这个最小值
思路
把编号作为边权,按最小生成树重构原图,求最大值最小显然可以二分,每次checkcheckcheck只需要判断从xxx和yyy尽可能往上跳到边权≤mid\leq mid≤mid的虚点,判断这两个虚点最多能访问多少不同节点即可,这部分可以预处理之后倍增lognlognlogn实现,有个细节是若xxx和yyy最后重合了,就只返回siz\[x\]
代码
cpp
const int N=2e5+10;
struct edge{
int u,v,w;
bool operator < (const edge&t)const
{
return w<t.w;
}
};
int p[N];
int fa[N][20];
int siz[N];
vector<int>e[N];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=2*n;i++) p[i]=i;
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
e1[i]={a,b,i};
}
sort(all(e1));
int cnt=n;
vector<int>w1(2*n+1);
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
p[x]=cnt;
p[y]=cnt;
w1[cnt]=w;
}
}
auto dfs=[&](auto &&dfs,int u,int father)->void
{
fa[u][0]=father;
if(u<=n) siz[u]=1;
for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(dfs,ed,u);
siz[u]+=siz[ed];
}
};
for(int i=1;i<=cnt;i++) if(i==p[i]) dfs(dfs,i,0);
auto check=[&](int x,int y,int limit)
{
for(int i=19;i>=0;i--)
{
if(fa[x][i] && w1[fa[x][i]]<=limit) x=fa[x][i];
if(fa[y][i] && w1[fa[y][i]]<=limit) y=fa[y][i];
}
if(x==y) return siz[x];
else return siz[x]+siz[y];
};
int q;cin>>q;
while(q--)
{
int x,y,z;
cin>>x>>y>>z;
int l=0,r=m+1;
while(l+1<r)
{
int mid=(l+r)>>1;
//cout<<check(x,y,mid)<<endl;
if(check(x,y,mid)>=z) r=mid;
else l=mid;
}
cout<<r<<endl;
}
}
P4768 归程
题目描述
本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。
魔力之都可以抽象成一个 nnn 个节点、mmm 条边的无向连通图(节点的编号从 111 至 nnn)。我们依次用 l,al,al,a 描述一条边的长度、海拔。
作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边 。我们用水位线 来描述降雨的程度,它的意义是:所有海拔不超过 水位线的边都是有积水的。
Yazid 是一名来自魔力之都的 OIer,刚参加完 ION2018 的他将踏上归程,回到他温暖的家。Yazid 的家恰好在魔力之都的 111 号节点。对于接下来 QQQ 天,每一天 Yazid 都会告诉你他的出发点 vvv ,以及当天的水位线 ppp。
每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。
需要特殊说明的是,第二天车会被重置,这意味着:
- 车会在新的出发点被准备好。
- Yazid 不能利用之前在某处停放的车。
Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。
本题的部分测试点将强制在线,具体细节请见【输入格式】和【子任务】。
单个测试点中包含多组数据。输入的第一行为一个非负整数 TTT,表示数据的组数。
接下来依次描述每组数据,对于每组数据:
第一行 222 个非负整数 n,mn,mn,m,分别表示节点数、边数。
接下来 mmm 行,每行 444 个正整数 u,v,l,au, v, l, au,v,l,a,描述一条连接节点 u,vu, vu,v 的、长度为 lll、海拔为 aaa 的边。
在这里,我们保证 1≤u,v≤n1 \leq u,v \leq n1≤u,v≤n。
接下来一行 333 个非负数 Q,K,SQ, K, SQ,K,S ,其中 QQQ 表示总天数,K∈0,1K \in {0,1}K∈0,1 是一个会在下面被用到的系数,SSS 表示的是可能的最高水位线。
接下来 QQQ 行依次描述每天的状况。每行 222 个整数 v0,p0v_0, p_0v0,p0 描述一天:
- 这一天的出发节点为 v=(v0+K×lastans−1) mod n+1v = (v_0 + K \times \mathrm{lastans} - 1) \bmod n + 1v=(v0+K×lastans−1)modn+1。
- 这一天的水位线为 p=(p0+K×lastans) mod (S+1)p = (p_0 + K \times \mathrm{lastans}) \bmod (S + 1)p=(p0+K×lastans)mod(S+1)。
其中 lastans\mathrm{lastans}lastans 表示上一天的答案(最小步行总路程)。特别地,我们规定第 111 天时 lastans=0\mathrm{lastans} = 0lastans=0。
在这里,我们保证 1≤v0≤n1 \leq v_0 \leq n1≤v0≤n,0≤p0≤S0 \leq p_0 \leq S0≤p0≤S。
对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。
所有测试点均保证 T≤3T\leq 3T≤3,所有测试点中的所有数据均满足如下限制:
- n≤2×105n\leq 2\times 10^5n≤2×105,m≤4×105m\leq 4\times 10^5m≤4×105,Q≤4×105Q\leq 4\times 10^5Q≤4×105,K∈{0,1}K\in\left\{0,1\right\}K∈{0,1},1≤S≤1091\leq S\leq 10^91≤S≤109。
- 对于所有边:l≤104l\leq 10^4l≤104,a≤109a\leq 10^9a≤109。
- 任意两点之间都直接或间接通过边相连。
思路
大概意思就是可以坐车走边权>y>y>y的边,这个代价是免费的,然后选择一个点下车,步行到111号点,求步行的最小代价,首先预处理出所有点到111号点的最短路,因为只能走>y>y>y的边,我们肯定希望尽可能的坐车走免费的边,那么我们按最大生成树重构原图,看每次坐车能获得的最大连通性在哪里,从这个虚点的叶子节点里选一个到111号点路径最短的点即可,这一步可以在dfsdfsdfs的时候预处理出来,这样每次查询就可以用倍增lognlognlogn实现
代码
cpp
const int N=4e5+10;
struct edge{
int u,v,w;
bool operator < (const edge&t)const
{
return w>t.w;
}
};
struct node{
ll v,w;
};
vector<node>e[N];
int p[N];
vector<int>tr[N];
int fa[N][20];
ll mndis[N];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void solve()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=2*n;i++)
{
e[i].clear();
tr[i].clear();
p[i]=i;
mndis[i]=INF;
for(int j=0;j<=19;j++) fa[i][j]=0;
}
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
e1[i]={a,b,d};
e[a].pb({b,c});
e[b].pb({a,c});
}
sort(all(e1));
int cnt=n;
vector<int>w1(2*n+1);
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
tr[cnt].pb(x);
tr[x].pb(cnt);
tr[cnt].pb(y);
tr[y].pb(cnt);
p[x]=cnt;
p[y]=cnt;
w1[cnt]=w;
}
}
priority_queue<PII,vector<PII>,greater<PII>>pq;
vector<ll>dis(n+1,INF);
vector<int>vis(n+1);
dis[1]=0;
pq.push({0,1});
while(!pq.empty())
{
auto [cost,cur]=pq.top();
pq.pop();
if(vis[cur]) continue;
vis[cur]=1;
for(auto [v,w]:e[cur])
{
if(cost+w<dis[v])
{
dis[v]=cost+w;
pq.push({cost+w,v});
}
}
}
auto dfs=[&](auto &&dfs,int u,int father)->void
{
fa[u][0]=father;
if(u<=n) mndis[u]=dis[u];
for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto ed:tr[u])
{
if(ed==father) continue;
dfs(dfs,ed,u);
}
for(auto ed:tr[u]) mndis[u]=min(mndis[u],mndis[ed]);
};
for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(dfs,i,0);
int q,k,s;
cin>>q>>k>>s;
int last=0;
auto query=[&](int x,int y)
{
for(int i=19;i>=0;i--)
{
if(fa[x][i] && w1[fa[x][i]]>y) x=fa[x][i];
}
return mndis[x];
};
while(q--)
{
int x,y;
cin>>x>>y;
x=(x+k*last-1)%n+1;
y=(y+k*last)%(s+1);
int ans=query(x,y);
cout<<ans<<endl;
last=ans;
}
}
cf1706E
题意
给定nnn个点mmm条边的无向连通图以及qqq次询问,每次询问要要加入前几条边可以使[l,r][l,r][l,r]之间的所有点互相连通
思路
我们以边的编号为边权,按最小生成树重构图,可以发现此时的答案就是[l,r][l,r][l,r]内区间所有点的lcalcalca所对应的点权,问题变为怎么快速求出一段区间的lcalcalca, 利用dfndfndfn序不难发现[l,r][l,r][l,r]的lcalcalca就是dfnmax∈[l,r]dfn_{max} \in [l,r]dfnmax∈[l,r]和dfnmin∈[l,r]dfn_{min} \in [l,r]dfnmin∈[l,r]的lcalcalca, 只需要在dfsdfsdfs过程中为所有点分配dfndfndfn序并且建立dfndfndfn序到点编号的映射即可,[l,r][l,r][l,r]内dfndfndfn序最大和最小的点可以使用两个ststst表维护
代码
cpp
const int N=2e5+10;
struct edge{
int u,v,w;
bool operator < (const edge&t) const
{
return w<t.w;
}
};
int p[N];
int dep[N];
int fa[N][20];
int tot=0;
int dfn[N],idx[N];
int st1[N][21];
int st2[N][21];
vector<int>e[N];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void dfs(int u,int father)
{
fa[u][0]=father;
dep[u]=dep[father]+1;
dfn[u]=++tot;
idx[tot]=u;
for(int i=1;(1<<i)<=dep[u];i++)
{
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(ed,u);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
{
if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
if(u==v) return v;
}
for(int i=19;i>=0;i--)
{
if(fa[u][i]!=fa[v][i])
{
u=fa[u][i];
v=fa[v][i];
}
}
return fa[u][0];
}
int query_max(int l,int r)
{
int k=log2(r-l+1);
return max(st1[l][k],st1[r-(1<<k)+1][k]);
}
int query_min(int l,int r)
{
int k=log2(r-l+1);
return min(st2[l][k],st2[r-(1<<k)+1][k]);
}
void solve()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=2*n;i++)
{
e[i].clear();
p[i]=i;
dep[i]=dfn[i]=idx[i]=0;
for(int j=0;j<=19;j++) fa[i][j]=0;
}
tot=0;
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
e1[i]={a,b,i};
}
sort(all(e1));
vector<int>w1(2*n+1);
int cnt=n;
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
p[x]=cnt;
p[y]=cnt;
w1[cnt]=w;
}
}
for(int i=1;i<=cnt;i++) if(p[i]==i) dfs(i,0);
for(int i=1;i<=cnt;i++) st1[i][0]=st2[i][0]=dfn[i];
for(int j=1;j<=20;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
st1[i][j]=max(st1[i][j-1],st1[i+(1<<(j-1))][j-1]);
st2[i][j]=min(st2[i][j-1],st2[i+(1<<(j-1))][j-1]);
}
}
while(q--)
{
int l,r;
cin>>l>>r;
int x=query_max(l,r);
int y=query_min(l,r);
cout<<w1[lca(idx[x],idx[y])]<<" ";
}
cout<<endl;
}
2021上海H Life is a Game
题意
一张nnn点mmm条边的有权无向图,每个点有点权aia_iai, 每到达一个点,就可以使声望值增加aia_iai点,对于边jjj其边权为wjw_jwj,只有当前声望值>wj> w_j>wj时才可以通过边www, 给出qqq个询问,每次给出出发点和初始声望值,求从该点出发可以获得的最大声望值
1≤n,m,q≤1e51 \leq n,m,q \leq 1e51≤n,m,q≤1e5
思路
用最小生成树重构原图,处理每个虚点作为根节点时可以获得的总声望值 get_i ,对于每次询问倍增处理,从出发点跳到当前声望可以到达的最高点,即 res+get[u]≥w[u]res+get[u] \geq w[u]res+get[u]≥w[u], w\[u\]为重构之后的虚点点权,需要注意的是每次可能向上跳多次,因为每跳到新的一个点声望值都会增加,需要判断是否还能接着往上跳
代码
cpp
const int N=2e5+10;
struct edge{
int u,v,w;
bool operator < (const edge &t)const
{
return w<t.w;
}
};
int p[N];
vector<int>e[N];
int dep[N];
int fa[N][20];
int find(int x)
{
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void solve()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=2*n;i++) p[i]=i;
vector<int>a(n+1);
for(int i=1;i<=n;i++) cin>>a[i];
vector<edge>e1(m+1);
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
e1[i]={a,b,c};
}
sort(all(e1));
vector<int>w1(2*n+1);
int cnt=n;
for(int i=1;i<=m;i++)
{
auto [u,v,w]=e1[i];
int x=find(u);
int y=find(v);
if(x!=y)
{
cnt++;
e[cnt].pb(x);
e[x].pb(cnt);
e[cnt].pb(y);
e[y].pb(cnt);
p[x]=cnt;
p[y]=cnt;
w1[cnt]=w;
}
}
vector<ll>get(cnt+1);
auto dfs=[&](auto &&dfs,int u,int father)->void
{
fa[u][0]=father;
if(u<=n) get[u]=a[u];
for(int i=1;i<=19;i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(auto ed:e[u])
{
if(ed==father) continue;
dfs(dfs,ed,u);
get[u]+=get[ed];
}
};
for(int i=1;i<=cnt;i++) if(i==p[i]) dfs(dfs,i,0);
auto query=[&](int x,ll y)
{
ll res=y+a[x];
int ok=1;
while(ok)
{
ok=0;
for(int i=19;i>=0;i--)
{
if(fa[x][i] && res>=w1[fa[x][i]])
{
x=fa[x][i];
res=y+get[x];
ok=1;
}
}
}
return res;
};
while(q--)
{
int x,y;
cin>>x>>y;
//cout<<get[x]<<endl;
cout<<query(x,y)<<endl;
}
}