众所周知,一旦建出的最小生成树需要遍历,Kruskal就需要大量只在某一阶段生效的存图空间,即kruskal排序+贪心时所用空间和链式前向星存树所用空间(其他存树方式后者只会更大)。
图的边数和树的边数*2的最大值,是我们理论上所需要的最小空间(因为需要同时存储),另一部分空间则成为了理论上可以优化的空间,虽然这部分多用的空间并不大,但是我们可以优化掉。
一种能降低kruskal后续空间的方法是我们可以临时开空间储存,实现贪心+排序后这部分空间的释放,但是并没有改变我们峰值时需要开额外,依然有优化空间。
我们考虑数组越界但是成功访问的原理:空间按照声明的顺序顺次开启,当我们访问一个越界的下标时,如果这个下标所对应的空间已经被其他数组开到了,那么此时不会RE,而是访问到所对应的空间。
那么,我们不妨只给第一部分node1个空间,随后开edge数组的大小开成max{图的边数,树的边数*2}的空间,从node的2位置开始读入,即edge的1位置,让对node无意义的空间立马被edge复用。
此时node每向后遍历一个边,edge最多拓展两个,显然可能使node未遍历任然有意义的边被覆盖,那么我们给node开额外三个无意义的变量a,b,c,就能使得有意义的node始终不会被覆盖。
后续优化:依旧需要开6个int大小的额外空间,理论上可以优化,但是意义不大。
贺的时候注意:
将edge数组的大小开成max{图的边数,树的边数*2}。
部分版本较老的C++编译器,或者部分编译指令可能会使得这个技巧爆RE
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int MAXN =305;
const int MAXM =1e5+5;
struct Node{int fm,to,val,a,b,c;
}node[1];
struct Edge{int nx,to,val;
}edge[2*MAXM];
int head[MAXN],cnt;
int n,m;
void add(int fm,int to,int val){edge[++cnt].nx=head[fm];edge[cnt].to=to;edge[cnt].val=val;head[fm]=cnt;
}
int f[MAXN];
int mx;bool cmp(Node a,Node b){return a.val<b.val;
}
int find(int x){return f[x]=f[x]==x?x:find(f[x]);
}void merge(int x,int y){x=find(x),y=find(y);if(x!=y){f[x]=y;}
}void Kruskal(){for(int i=1;i<=n;i++)f[i]=i;sort(node+2,node+m+2,cmp);for(int i=2;i<=m+1;i++){//cout<<node[i].fm<<' '<<node[i].to<<' '<<node[i].val<<endl;int x=node[i].fm,y=node[i].to;int tx=find(x),ty=find(y);if(tx!=ty){mx=node[i].val;merge(tx,ty);add(x,y,mx);add(y,x,mx);}}
}int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>m;int u,v,w;for(int i=2;i<=m+1;i++){cin>>u>>v>>w;node[i]={u,v,w,0,0,0};}Kruskal();cout<<mx<<endl;return 0;
}