MST 专题
MST 专题
Problem A. CF1305G Kuroni and Antihype
Description
有一个初始为空的集合
- 加入一个不在
中的元素 。此操作没有收益。 - 在集合
中找到一个 使得 ,获得 的收益,再把 加入到 中。
你需要将
Solution
考虑图论建模。
建立一个超级源点
那么我们只需要考虑第二种结构。可以发现,我们将元素加入集合的顺序形成了一棵以
但我们现在树上的边权与边的方向有关。
我们考虑这样建图:所有
这样我们最终生成的树中每个点的权值都在父亲处多算了一次。于是答案就是图的最大生成树权值减去
接下来考虑如何快速地求最大生成树。
利用 kruskal 的思想,我们从大到小枚举边权。对于一个边权为
我们先把所有点权相等的点缩为一个,记录每个权值出现的次数。
假设我们枚举到了
如果两者不在一个连通块内,那么我们合并两个点,对答案造成
我们还需要合并
时间复杂度为
int n,m,a[N];
ll ans,cnt[N],fa[N],siz[N];
int Find(int x){
if(fa[x]==x) return x;
return fa[x]=Find(fa[x]);
}
void Merge(int u,int v,ll w){
u=Find(u),v=Find(v);
if(u==v) return;
ans+=w*(cnt[u]+cnt[v]-1);
if(siz[u]>siz[v]) swap(u,v);
fa[u]=v; siz[v]+=siz[u]; cnt[v]=1;
}
signed main(){
//效率!效率!
read(n); cnt[0]=1;
for(int i=1;i<=n;i++){
read(a[i]);
Ckmax(m,a[i]);
ans-=a[i];
cnt[a[i]]++;
}
int AS=1;
while(AS<=m) AS<<=1;
AS--;
for(int i=0;i<=AS;i++) fa[i]=i,siz[i]=1;
for(int s=AS;s>=0;s--){
for(int t=s;;t=(t-1)&s){
if(t<(s^t)) break;
if(cnt[t]&&cnt[s^t])
Merge(s^t,t,s);
if(!t) break;
}
}
printf("%lld\n",ans);
return 0;
}
Problem B. [JSOI2008] 最小生成树计数
Description
现在给出了一个简单无向加权图。求出这个图中有多少个不同的最小生成树。(如果两棵最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。输出方案数对
数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过
对于全部数据,
Solution
引理:在 kruskal 的过程中,对于权值相同的边,无论以何种顺序处理,最终形成的连通块都是一样的。
正确性显然。如果两种处理顺序导致形成的连通块不同,那么这意味着我们可以在这两种方式中再多连几条权值相同的边,最终答案会变的更优。
利用 kruskal 的思想,从小到大枚举边权。
根据上面的引理,我们每次把边权相同的边拿出来一块处理。
由于题目保证了具有相同权值的边不会超过
利用乘法原理,我们把每一次的答案乘起来,就是最终的答案。
时间复杂度
int n,m;
struct Edge{
int u,v,w;
bool operator < (const Edge& tmp) const{
return w<tmp.w;
}
};
vector<Edge> edge;
vector<Edge> e[M];
int fa[N],tot,siz[N];
int res,cnt;
ll ans;
const ll mod=31011;
inline ll Mod(ll x){return (x>=mod)?(x-mod):(x);}
inline void Add(ll &x,ll y){x=Mod(x+y);}
int Find(int x){
if(fa[x]==x) return x;
return Find(fa[x]);
}
void dfs(int x,int c,vector<Edge> &s){
if(x==(signed)s.size()){
if(c>res) res=c,cnt=1;
else if(c==res) cnt++;
return;
}
int u=s[x].u,v=s[x].v;
int fu=Find(u),fv=Find(v);
if(fu!=fv){
int w=0;
if(siz[fu]>siz[fv]) swap(fu,fv);
fa[fu]=fv,siz[fv]+=siz[fu],w=siz[fu];
dfs(x+1,c+1,s);
fa[fu]=fu,siz[fv]-=w;
}
dfs(x+1,c,s);
}
signed main(){
//效率!效率!
read(n),read(m);
for(int i=1;i<=m;i++){
int u,v,w;
read(u),read(v),read(w);
edge.push_back({u,v,w});
}
sort(edge.begin(),edge.end());
e[0].push_back({0,0,0});
for(Edge i:edge){
if(i.w==e[tot].back().w) e[tot].push_back(i);
else e[++tot].push_back(i);
}
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
ans=1;
for(int i=1;i<=tot;i++){
res=0,cnt=0;
dfs(0,0,e[i]);
(ans*=cnt)%=mod;
for(Edge j:e[i]){
int u=j.u,v=j.v;
u=Find(u),v=Find(v);
if(u!=v){
if(siz[u]>siz[v]) swap(u,v);
fa[u]=v,siz[v]+=siz[u];
}
}
}
for(int i=2;i<=n;i++){
if(Find(i)!=Find(1))
return puts("0"),0;
}
printf("%lld\n",ans);
return 0;
}
Problem C. P5633 最小度限制生成树 (值得思考)
Description
给你一个有
Solution
首先我们把
我们对这些连通块求出一个 MST 森林。不在 MST 森林上的边在最后的答案中一定不会出现。
如果无解,当且仅当满足以下条件之一:
- 连通块个数
; - 存在一个连通块与
没有连边; 的度数 。
我们只保留森林上的边。对于每一个连通块,我们找出其与
如果
我们找出一个还没有向
我们从大到小枚举要断掉的边,找出断掉这条边后分出的两个连通块
但分裂操作不好维护。我们把这个过程倒过来,发现和 kruskal 的过程非常相似。
我们在 kruskal 中对每个点维护
我们把
kruskal 算法结束后,我们找出
int n,m,k,s;
struct Edge{
int u,v,w;
bool operator<(const Edge& tmp)const{
return w<tmp.w;
}
};
vector<Edge> e;
int fa[N],dis[N],val[N],p[N];
int Find(int x){
if(fa[x]==x) return x;
return fa[x]=Find(fa[x]);
}
void Merge(int u,int v,int w){
if(dis[u]<dis[v]) swap(u,v);
fa[u]=v; val[u]=dis[u]-w;
}
bool Cmp1(int x,int y){
return dis[x]<dis[y];
}
bool Cmp2(int x,int y){
return val[x]<val[y];
}
signed main(){
read(n),read(m),read(s),read(k);
memset(dis,0x3f,sizeof(dis));
memset(val,0x3f,sizeof(val));
for(int i=1;i<=m;i++){
int u,v,w;
read(u),read(v),read(w);
if(u==s) Ckmin(dis[v],w);
else if(v==s) Ckmin(dis[u],w);
else e.push_back({u,v,w});
}
sort(e.begin(),e.end());
for(int i=1;i<=n;i++) fa[i]=i;
ll ans=0;
for(Edge i:e){
int u=i.u,v=i.v,w=i.w;
u=Find(u),v=Find(v);
if(u==v) continue;
Merge(u,v,w); ans+=w;
}
int cnt=0;
for(int i=1;i<=n;i++) p[i]=i;
sort(p+1,p+n+1,Cmp1);
for(int i=1;i<=n;i++){
int x=p[i];
if(dis[x]>1e9) break;
if(Find(x)!=x) continue;
ans+=dis[x]; val[x]=IINF; cnt++;
}
if(cnt>k) return puts("Impossible"),0;
sort(p+1,p+n+1,Cmp2);
for(int i=1;i<=n;i++){
if(cnt==k) break;
int x=p[i];
if(val[x]>1e9)
return puts("Impossible"),0;
ans+=val[x],cnt++;
}
printf("%lld\n",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】