Kruskal
Kruscal最小生成树算法,将边按照权值进行排序,每次贪心优先选择权值较小的边,并依次连接,若出现环则跳过,边数达到节点数-1时即可停止。时间复杂度O(Mlog2(M))
inline int kruskal(){/*最小生成树用于无向图,连边时可以按照单向边来连*/
sort(e+1,e+1+tot);/*按照边权升序排序*/
d.init(n);
int ans=0,cnt=0;
for(int i=1;i<=tot;i++){
int x=e[i].from,y=e[i].to;
if(d.merge(x,y))ans+=e[i].w,cnt++;/*并查集维护,尚未成环则加入生成树,统计生成树中边的数量*/
if(cnt==n-1)break;/*终止条件*/
}
if(cnt!=n-1)return false;/*图不连通*/
return ans;
}
Kruscal重构树是在进行Kruscal算法时,对于每次选择的边新建一个节点,点权赋值为所选择边的边权,该点向边的两个端点连边。重构树的n个叶子为原图中的n个节点且没有点权,重构树满足大根堆或小根堆性质,x和y点的lca的点权就对应着它们最小生成树上的瓶颈。
inline void exkruscal(){
int tot=0,cnt=n;
d.init(n<<1);
sort(e+1,e+1+m);
for(int i=1;i<=m;i++){
int x=d.find(e[i].x),y=d.find(e[i].y);
if(x==y)continue;
++tot;
add(++cnt,x);
add(cnt,y);
d.f[x]=d.f[y]=cnt;
p[cnt].a=e[i].a;
if(tot==n-1)break;
}
dfs(cnt,0);
}
一张无向图,每次询问一个点对[l,r]表示对于区间内的所有点,求一个k使得只走前k条边,都可以互相到达,前k条边指的是第1...k条边。将边的编号转化成看作边权,可以转化成kruscal重构树,几个点要互相到达,最小化经过的所有边权的最大值,对于求区间[l,r]的LCA,取其中dfs序最大和最小两者即可。
d.init(n<<1);
int tot=dfn=0,cnt=n;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
x=d.find(x),y=d.find(y);
if(x!=y&&tot<=n-2){
tot++;
p[++cnt]={x,y};
val[cnt]=i;
d.f[x]=d.f[y]=cnt;
}
}
dfs(cnt,0);
STTable();
while(q--){
int l,r;
cin>>l>>r;
cout<<val[LCA(mp[query(l,r,0)],mp[query(l,r,1)])]<<' ';
}
归程。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int n,m,q,s,k,h0[N<<1],h[N],cntx,dis[N],dep[N],f[N][25],totx;
bool vis[N];
struct Node{
int x,y,l,a;
inline bool friend operator<(const Node&x,const Node&y){
return x.a>y.a;
}
}e[N<<2],p[N<<1];
struct edge{
int to,next;
}tr[N<<2];
struct node{
int to,next,w;
}t[N<<1];
struct DSU{
int f[N<<1];
inline void init(int n){
for(int i=1;i<=n;i++)f[i]=i;
}
int find(int x){
return x==f[x]?x:f[x]=find(f[x]);
}
}d;
inline void dijkstra(int s){
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>>q;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
q.push({0,s});
dis[s]=0;
while(!q.empty()){
int x=q.top().second;
q.pop();
if(vis[x])continue;
vis[x]=1;
for(int i=h[x];i;i=t[i].next){
int y=t[i].to;
if(dis[y]>dis[x]+t[i].w){
dis[y]=dis[x]+t[i].w;
q.push({dis[y],y});
}
}
}
for(int i=1;i<=n;i++)p[i].l=dis[i];
}
inline void add(int x,int y){
tr[++cntx]={y,h0[x]},h0[x]=cntx;
}
inline void addx(int x,int y,int w){
t[++totx]={y,h[x],w},h[x]=totx;
}
void dfs(int x,int fa){
dep[x]=dep[fa]+1;
f[x][0]=fa;
for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
for(int i=h0[x];i;i=tr[i].next){
int y=tr[i].to;
dfs(y,x);
p[x].l=min(p[x].l,p[y].l);
}
}
inline int query(int x,int y){
for(int i=20;~i;i--)if(dep[x]-(1<<i)>0&&p[f[x][i]].a>y)x=f[x][i];
return p[x].l;
}
inline void exkruscal(){
int tot=0,cnt=n;
d.init(n<<1);
sort(e+1,e+1+m);
for(int i=1;i<=m;i++){
int x=d.find(e[i].x),y=d.find(e[i].y);
if(x==y)continue;
++tot;
add(++cnt,x);
add(cnt,y);
d.f[x]=d.f[y]=d.f[cnt]=cnt;
p[cnt].a=e[i].a;
if(tot==n-1)break;
}
dfs(cnt,0);
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t;cin>>t;
while(t--){
int last=0;
cin>>n>>m;
memset(e,0,sizeof(e));
cntx=totx=0;
memset(h0,0,sizeof(h0));
memset(h,0,sizeof(h));
memset(f,0,sizeof(f));
for(int i=1;i<=m;i++){
int x,y,l,a;
cin>>x>>y>>l>>a;
e[i]={x,y,l,a};
addx(x,y,l),addx(y,x,l);
}
for(int i=n+1;i<=(n<<1);i++)p[i].l=0x3f3f3f3f;
dijkstra(1);
cin>>q>>k>>s;
exkruscal();
while(q--){
int x,y;
cin>>x>>y;
x=(k*last+x-1)%n+1,y=(k*last+y)%(s+1);
cout<<(last=query(x,y))<<'\n';
}
}
return 0;
}
一张图每条边有两个属性gi和si,求一个生成树,是的gmax(g[i])+smax(s[i])最小。首先按照g升序,依次插入每条边,每插入一条边就将其按照s升序,之后构建最小生成树,每次不用重新排序,将新插入的边和之前边进行比较交换即可,由于之前用不到的边以后也不会用,可以记录上一次用到的边和边数。
sort(e+1,e+1+m);
for(int i=1;i<=m;i++){
a[++tot]=e[i];
for(int j=tot-1;j;j--)if(a[j].s>a[j+1].s)swap(a[j],a[j+1]);
d.init(n);
int cnt=0,gg=0,ss=0;
for(int i=1;i<=tot;i++){
int x=d.find(a[i].x),y=d.find(a[i].y);
if(x==y)continue;
d.merge(x,y);
a[++cnt]=a[i];
gg=max(gg,a[i].g),ss=max(ss,a[i].s);
}
if(cnt==n-1)ans=min(ans,g*gg+s*ss);
tot=cnt;
}
求严格次小生成树。严格次小生成树相比最小生成树肯定改变且仅改变了一条边,在建出kruskal生成树之后,枚举不在树上的每一条边添加,那么肯定形成一个环,且环上任意一条边的边权都不大于加入的边的边权,在环上找到严格小于加入的边的边权的边并将其删去,之后更新答案。倍增维护最大值和次大值,由于环上边权均不大于加入的边的边权,所以当加入的边的边权和环上最大值相等时,环上次大值即为严格小于加入的边的边权的边权,否则取环上最大值。
inline void kruskal(){
sort(edge+1,edge+1+m);
dsu.init(n);
int tot=0;
for(int i=1;i<=m;i++){
int x=dsu.find(edge[i].x),y=dsu.find(edge[i].y);
if(x==y)continue;
dsu.merge(x,y);
sum+=edge[i].w;
add(edge[i].x,edge[i].y,edge[i].w);
add(edge[i].y,edge[i].x,edge[i].w);
if(++tot==n-1)break;
}
}
void dfs(int x,int f){
dep[x]=dep[f]+1;
fa[x][0]=f;
for(int i=1;i<=18;i++){
int f=fa[x][i-1];
fa[x][i]=fa[f][i-1];
if(ma[x][i-1][0]==ma[f][i-1][0]){//两段最大值相等
ma[x][i][0]=ma[x][i-1][0];//直接赋值
ma[x][i][1]=max(ma[x][i-1][1],ma[f][i-1][1]);//次大值在两段中取max
}
else if(ma[x][i-1][0]>ma[f][i-1][0]){//左边较大
ma[x][i][0]=ma[x][i-1][0];//最大值选左边
ma[x][i][1]=max(ma[x][i-1][1],ma[f][i-1][0]);//右边最大值有可能成为次大值
}
else{//右边较大
ma[x][i][0]=ma[f][i-1][0];//最大值选右边
ma[x][i][1]=max(ma[x][i-1][0],ma[f][i-1][1]);//左边最大值有可能成为次大值
}
}
for(int i=h[x];i;i=e[i].next){
int y=e[i].to;
if(y==f)continue;
ma[y][0][0]=e[i].w;//边权最大值
ma[y][0][1]=-1;//边权次大值
dfs(y,x);
}
}
inline void cmax(int&x,int w,int a[2]){x=max(x,a[w==a[0]]);}
inline int LCA(int x,int y,int w){
int re=-1;
if(dep[x]<dep[y])swap(x,y);
for(int i=18;~i;i--)if(dep[fa[x][i]]>=dep[y])cmax(re,w,ma[x][i]),x=fa[x][i];
if(x==y)return re;
for(int i=18;~i;i--){
if(fa[x][i]!=fa[y][i]){
cmax(re,w,ma[x][i]);
cmax(re,w,ma[y][i]);
x=fa[x][i];
y=fa[y][i];
}
}
cmax(re,w,ma[x][0]);
cmax(re,w,ma[y][0]);
return re;
}
for(int i=1;i<=m;i++){
if(vis[i])continue;
int re=LCA(edge[i].x,edge[i].y,edge[i].w);
if(re==-1)continue;
ans=min(ans,sum-re+edge[i].w);
}