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; 
} 
posted on 2025-11-03 16:38  _Liuliuliuliuliu  阅读(11)  评论(0)    收藏  举报