最小生成树
\(Prim\):适用于稠密图,也可以使用类似于\(Dijkstra\)的优化方式。
\(Kruskal\):本质上是一种贪心,利用贪心的性质可解决一些最优化问题。
\(Boruvka\):一般在具有特殊性质的完全图中配合数据结构使用。
以上三种算法,是解决最小生成树问题的基础,请读者务必详细了解每种算法的本质原理。
一道用\(Prim\)和\(Kruskal\)算法难以解决,但\(Boruvka\)算法容易解决的题目。
\(CF1550F\)
https://www.luogu.com.cn/problem/CF1550F
题意:数轴上\(n\)个点,初始在点\(s\),有一个常量\(d\)和一个变量\(k\),点\(i\)能跳到点\(j\),当且仅当\(d-k\leq |a_i-a_j|\leq d+k\)。
多次询问\(k,x\),回答在变量为\(k\)的情况下,能否从\(s\)至\(x\)。
题解:边\((i,j)\)的存在性关于\(k\)显然存在单调性,于是考虑令\(t(i,j)\)为最小的\(k\)使得边\((i,j)\)存在。
我们要找一条从\(s\)至\(x\)路径,使得路径上边最小值大于等于\(k\),显然是让最小值最大,即瓶颈路。
按\(t(i,j)\)从大到小建立生成树,树上\(s\)至\(x\)路径的边权最小值,即为所求路径。
考虑完全图中用\(Boruvka\)求最短路。
本题中要求对于\(i\)找到一个\(j\),满足\(i,j\)当前不在同一连通块,且\(||a_i-a_j|-d|\)最小。
若\(i>j,||a_i-a_j|-d|=|a_i-d-a_j|\)。
若\(i<j,||a_i-a_j|-d|=|a_j-a_i-d|=|a_i+d-a_j|\)。
于是用\(set\)存点,查询某连通块时,先在\(set\)中删除连通块内所有点,然后二分查找即可,查询完将连通块内点重新加入\(set\)即可。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,s,d,cnt;
int p[N],a[N],id[N],d1[N];
int h[N],e[N],w[N],ne[N],idx;
bool st[N];
set<int> S;
vector<int> q[N];
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int find(int x){
if(x==p[x]) return x;
return find(p[x]);
}
void merge(int x,int y,int z){
x=find(x),y=find(y);
if(x==y) return;
p[x]=y;
add(x,y,z),add(y,x,z);
}
void dfs(int u,int fa,int dist){
d1[u]=dist,st[u]=1,q[cnt].push_back(u);
for(int i=h[u]; i!=-1; i=ne[i]){
int j=e[i];
if(j==fa) continue;
dfs(j,u,max(dist,w[i]));
}
}
int main(){
memset(h,-1,sizeof h);
cin >> n >> m >> s >> d;
for(int i=1; i<=n; i++) cin >> a[i];
for(int i=1; i<=n; i++) id[a[i]]=i;
for(int i=1; i<=n; i++) p[i]=i;
for(int i=1; i<=n; i++) S.insert(a[i]);
while(1){
for(int i=1; i<=cnt; i++){
for(int j=0; j<q[i].size(); j++) S.erase(a[q[i][j]]);
int mi=1e9,pos;
for(int j=0; j<q[i].size(); j++){
set<int>::iterator it;
it=S.lower_bound(a[q[i][j]]-d);
if(it!=S.end())
if(id[*it]<q[i][j])
if(abs(abs(a[q[i][j]]-(*it))-d)<mi)
mi=abs(abs(a[q[i][j]]-(*it))-d),pos=id[*it];
if(it!=S.begin()){
it--;
if(id[*it]<q[i][j])
if(abs(abs(a[q[i][j]]-(*it))-d)<mi)
mi=abs(abs(a[q[i][j]]-(*it))-d),pos=id[*it];
}
it=S.lower_bound(a[q[i][j]]+d);
if(it!=S.end())
if(id[*it]>q[i][j])
if(abs(abs(a[q[i][j]]-(*it))-d)<mi)
mi=abs(abs(a[q[i][j]]-(*it))-d),pos=id[*it];
if(it!=S.begin()){
it--;
if(id[*it]>q[i][j])
if(abs(abs(a[q[i][j]]-(*it))-d)<mi)
mi=abs(abs(a[q[i][j]]-(*it))-d),pos=id[*it];
}
}
merge(q[i][0],pos,mi);
for(int j=0; j<q[i].size(); j++) S.insert(a[q[i][j]]);
}
memset(st,0,sizeof st);
cnt=0;
for(int j=1; j<=n; j++)
if(!st[j]) q[++cnt].clear(),dfs(j,-1,0);
if(cnt==1) break;
}
dfs(s,-1,0);
for(int i=1; i<=m; i++){
int x,y;
cin >> x >> y;
if(d1[x]<=y) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
\(Kruskal\)重构树是在\(Kruskal\)求最小生成树过程中,将每次加入一条树边,改为增加一个新点,点权存储这条边的信息,并从新点向树边两端点所处子树根连边。这样可将两点间信息,转化为两点\(lca\)的点权信息。
一道\(Kruskal\)重构树同最短路相结合的例题。
\(P4768\)
https://www.luogu.com.cn/problem/P4768
题意:过于冗长,自行观看。
题解:先求出\(1\)号点到所有点的最短路,对于每次询问,考虑枚举停车点。若当天起点为\(x\),可用停车点\(y\)要满足,存在一条\(x\sim y\)路径,使得路径最低海拔大于等于\(p\),即最大化最小边权。考虑\(Kruskal\)重构树,重构树上从\(x\)向上跳,跳到满足\(p\leq a_k\),且深度最小的点\(k\)。回答以\(k\)为根子树的叶子节点中,到\(1\)距离最小的点即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
const int N=2e6+10;
int n,m,q,k,s,lg,root;
int p[N],dep[N],high[N],f[N][22];
int h[N],e[N],w[N],ne[N],idx;
bool st[N];
LL d[N];
struct Edge {
int x,y,z;
} E[N];
bool operator <(Edge a,Edge b) {
return a.z<b.z;
}
int find(int x) {
if(x!=p[x]) p[x]=find(p[x]);
return p[x];
}
void add(int a,int b,int c) {
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dijkstra() {
for(int i=1; i<=n; i++) st[i]=0;
for(int i=1; i<=2*n; i++) d[i]=1e16;
priority_queue<PLI,vector<PLI>,greater<PLI>> heap;
d[1]=0,heap.push({d[1],1});
while(heap.size()) {
int t=heap.top().second;
heap.pop();
if(st[t]) continue;
st[t]=1;
for(int i=h[t]; i!=-1; i=ne[i]) {
int j=e[i];
if(st[j]) continue;
if(d[t]+w[i]<d[j]) {
d[j]=d[t]+w[i];
heap.push({d[j],j});
}
}
}
}
void dfs(int u,int depth) {
dep[u]=depth;
if(u>=1&&u<=n) return;
for(int i=h[u]; i!=-1; i=ne[i]) {
int j=e[i];
f[j][0]=u;
for(int k=1; k<=lg; k++)
f[j][k]=f[f[j][k-1]][k-1];
dfs(j,depth+1);
d[u]=min(d[u],d[j]);
}
}
int get(int x,int y) {
for(int i=lg; i>=0; i--)
if(high[f[x][i]]>y) x=f[x][i];
return d[x];
}
int main() {
int T;
cin >> T;
while(T--) {
cin >> n >> m;
lg=(int)(log(n)/log(2))+1;
idx=0;
for(int i=1; i<=2*n; i++) h[i]=-1;
for(int i=1; i<=m; i++) {
int x,y,z,h;
cin >> x >> y >> z >> h;
add(x,y,z),add(y,x,z);
E[i]= {x,y,h};
}
dijkstra();
sort(E+1,E+m+1);
reverse(E+1,E+m+1);
for(int i=1; i<=2*n; i++) p[i]=i;
root=n;
for(int i=1; i<=m; i++) {
int x=E[i].x,y=E[i].y,z=E[i].z;
x=find(x),y=find(y);
if(x==y) continue;
high[++root]=z,p[x]=p[y]=root;
add(root,x,0),add(root,y,0);
}
for(int i=0; i<=lg; i++) f[root][i]=0;
dfs(root,1);
cin >> q >> k >> s;
for(int i=1,last=0; i<=q; i++) {
int v,p;
cin >> v >> p;
v=(v+k*last-1)%n+1;
p=(p+k*last)%(s+1);
last=get(v,p);
cout << last << endl;
}
}
return 0;
}
一道不寻常的\(Kruskal\)重构树题
\(P6765\)
https://www.luogu.com.cn/problem/P6765
题意:过于冗长,自行观看。
题解:传统的\(Kruskal\)重构树是在连树边,即连接两集合时,建立新点,于是两集合间点对路径的最值信息,转化为两点\(lca\)处信息。在本题中仍然用这种思想,不过建立新点的条件有所变换。
考虑两点间可达的条件,即两点间连通,且所处连通块非链。于是考虑维护连通块是否非链,若为链,用\(l,r\)表示链的左右端点。
同样按边权从小到大枚举边,关键信息是令连通块变成非链的那条边,于是考虑在连通块变为非链时候建新点,新点向连通块内所有点连边。
于是用并查集维护叶子节点连通性,当连通块变为非链连通块时,或非链连通块之间连通时,建立新点,新点连向变成非链连通块内的所有点,和原本处于非链连通块的根节点。这样做既维护同时连通且非链的性质。
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int n,m,lg;
int p[N],sz[N],pos[N][2];
int a[N],rt[N],dep[N],f[N][22];
bool st[N],st1[N];
vector<int> q[N],tr[N];
struct Edge{
int x,y,z;
bool operator <(Edge t){
return z<t.z;
}
}E[N];
struct Dsu{
void init(){
for(int i=1; i<=n; i++) p[i]=i;
}
int find(int x){
if(x==p[x]) return x;
return p[x]=find(p[x]);
}
}dsu;
void dfs(int u,int fa,int depth){
dep[u]=depth;
if(fa==-1) rt[u]=u;
else rt[u]=rt[fa];
if(u<=n) return;
for(int i=0; i<tr[u].size(); i++){
int j=tr[u][i];
f[j][0]=u;
for(int k=1; k<=lg; k++)
f[j][k]=f[f[j][k-1]][k-1];
//cout << u << " " << j << " OK" << endl;
dfs(j,u,depth+1);
}
}
int lca(int x,int y){
//cout << x << " " << y << " " << rt[x] << " " << rt[y] << endl;
if(rt[x]!=rt[y]) return -1;
if(dep[x]<dep[y]) swap(x,y);
for(int i=lg; i>=0; i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=lg; i>=0; i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void init(int N,int M,vector<int> U,vector<int> V,vector<int> W){
n=N,m=M;
lg=(int)(log(2*n)/log(2))+1;
for(int i=0; i<m; i++){
int x=U[i],y=V[i],z=W[i];
x++,y++;
E[i+1]=(Edge){x,y,z};
}
sort(E+1,E+m+1);
dsu.init();
for(int i=0; i<500000; i++) pos[i][0]=pos[i][1]=rt[i]=i,sz[i]=1,q[i].push_back(i);
int num=n;
for(int i=1; i<=m; i++){
int x=E[i].x,y=E[i].y,z=E[i].z,tx=x,ty=y;
x=dsu.find(x),y=dsu.find(y);
if(q[x].size()>q[y].size()) swap(x,y),swap(tx,ty);
if(x==y){
if(!st[x]){
st[x]=1;
a[++num]=z;
for(int j=0; j<q[x].size(); j++)
tr[num].push_back(q[x][j]);
rt[x]=num;
q[x].clear();
}
continue;
}
if(st[x]&&st[y]){
a[++num]=z;
tr[num].push_back(rt[x]);
tr[num].push_back(rt[y]);
p[x]=y,rt[x]=rt[y]=num;
continue;
}
if(st[x]&&!st[y]){
st[y]=1;
a[++num]=z;
tr[num].push_back(rt[x]);
for(int j=0; j<q[y].size(); j++)
tr[num].push_back(q[y][j]);
q[y].clear();
rt[y]=num;
p[x]=y;
continue;
}
if(!st[x]&&st[y]){
st[x]=1;
a[++num]=z;
tr[num].push_back(rt[y]);
for(int j=0; j<q[x].size(); j++)
tr[num].push_back(q[x][j]);
q[x].clear();
rt[y]=num;
p[x]=y;
continue;
}
bool flag=0;
for(int i=0; i<2; i++)
for(int j=0; j<2; j++)
for(int k=0; k<2; k++){
if(flag) continue;
if(k%2) swap(tx,ty);
if(pos[x][i]==tx&&pos[y][j]==ty){
flag=1;
int Tx=pos[x][1-i],Ty=pos[y][1-j];
for(int t=0; t<q[x].size(); t++)
q[y].push_back(q[x][t]);
q[x].clear();
p[x]=y;
pos[y][0]=Tx,pos[y][1]=Ty;
}
if(k%2) swap(tx,ty);
}
if(flag) continue;
a[++num]=z;
for(int j=0; j<q[x].size(); j++)
tr[num].push_back(q[x][j]);
q[x].clear();
for(int j=0; j<q[y].size(); j++)
tr[num].push_back(q[y][j]);
q[y].clear();
st[x]=st[y]=1;
rt[y]=num;
p[x]=y;
}
//for(int i=0; i<q[8].size(); i++) cout << q[8][i] << " OK";
//cout << endl;
for(int i=num; i; i--) if(!dep[i]) dfs(i,-1,1);
}
int getMinimumFuelCapacity(int X,int Y){
int x=X,y=Y;
x++,y++;
int p=lca(x,y);
if(p==-1) return -1;
return a[p];
}
int main(){
int n,m;
vector<int> U,V,W;
cin >> n >> m;
for(int i=1; i<=m; i++){
int x,y,z;
cin >> x >> y >> z;
U.push_back(x);
V.push_back(y);
W.push_back(z);
}
init(n,m,U,V,W);
int q;
cin >> q;
for(int i=1; i<=q; i++){
int x,y;
cin >> x >> y;
cout << getMinimumFuelCapacity(x,y) << endl;
}
return 0;
}
浙公网安备 33010602011771号