P14362 [CSP-S 2025] 道路修复 / road 题解
前言
考场上的时候笔者一直在想T2,结果到最后还是没有秒掉。
当时我在想什么??!!\(2^k\) 枚举轻松拿下?!
思路
首先看 \(k=0\) 的部分。此部分就是要求我们求最小生成树。很简答的 \(16 pts\)。
然后再看 \(k > 0\) 的部分。我们很难不注意到 \(k\) 的范围真的很小,于是大胆的猜测和 \(2^k\) 相关。
于是就想到了枚举状态。对于每个乡镇,都有选(\(1\))或不选(\(0\))两种状态。选的时候就把乡镇的改造费用加上,并且这个乡镇所有道路都可以参与最小生成树的计算。最后统计所有状态中最小的费用。
我们不难发现,对于原城市之间的道路仅有最小生成树的边会对答案产生贡献。于是可以先跑一边最小生成树(笔者这里写的是kruskal)。然后把所有乡镇到城市的边加进去。如果用不到这个乡镇就忽略这条边。
时间复杂度 \(O(m \log m + kn \log kn + 2^k kn)\)
易错点
- cmp中按照边权从小到大排序!
- 一开始的最小生成树e数组下标从 \(1\) 到 \(m\),而不是 \(tot\)。
- cnt 和 num 不要用混了!建议自己加注释。
代码如下
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
#define ___ __int128
#define INF 0x3f3f3f3f3f3f3f3f //注意要赋为LONGLONG_MAX
inline int Read(){
int x=0,f=1;
char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){x=x*10+c-48;c=getchar();}
return x*f;
}
inline void Write(int x){
if(x<0) {putchar('-');x=-x;}
if(x>9) Write(x/10);
putchar(x%10+'0');
}
const int N=1e4+10,M=1e6+10;
int n,m,k,c[20];
int tot=0,ans=0,cnt=0;
int fa[N+20];
bool vis[20];
struct edge{
int from,to,w;
}e[M<<1];//边应该开M+K*N
bool cmp(edge A,edge B){return A.w<B.w;}
int findf(int x){
if(fa[x]==x) return x;
return fa[x]=findf(fa[x]);
}
signed main(){
n=Read();m=Read();k=Read();
for(int i=1;i<=m;i++){
int u=Read(),v=Read(),w=Read();
e[i]=(edge){u,v,w};
}
//kruskal板子
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;//别忘了赋初值
for(int i=1;i<=m;i++){
int fx=findf(e[i].from),fy=findf(e[i].to);
if(fx==fy) continue;
fa[fx]=fy;
e[++tot]=e[i];
cnt++;
if(cnt>=n-1) break;
}
for(int i=1;i<=k;i++){
c[i]=Read();
for(int j=1;j<=n;j++){
int w=Read();
e[++tot]=(edge){j,i+n,w};
}
}
//所有边排序
sort(e+1,e+tot+1,cmp);
ans=INF;//赋极大值
for(int s=0;s<(1<<k);s++){//枚举每个乡镇状态
int t=0,num=0;//num:最小生成树需要几条边
cnt=0;//当前连了几条边
for(int i=1;i<=k;i++) vis[i]=0;//每个状态都要初始化
for(int i=1;i<=n+k;i++) fa[i]=i;//每个状态都要初始化
for(int i=1;i<=k;i++){
if((s>>(i-1))&1) {
vis[i]=1;
t+=c[i];//t:此状态下的费用
num++;
}
}
num+=n-1;//要加上原先的最小生成树的 n-1 条边
for(int i=1;i<=tot;i++){
if(e[i].to>n&&!vis[e[i].to-n]) continue;//如果这个乡镇没有选,不用这条边
int fx=findf(e[i].from),fy=findf(e[i].to);
if(fx!=fy){
fa[fx]=fy;
t+=e[i].w;
cnt++;
if(cnt>=num) break;
}
}
if(cnt>=num) ans=min(ans,t);//更新答案
}
printf("%lld\n",ans);
return 0;
}
浙公网安备 33010602011771号