P14362 [CSP-S 2025] 道路修复 / road
考试的时候想到是个最小生成树的来着,先码了 \(O(m \log m+2^km)\) 的解法,预计 64 分。赛后才想明白可以把 \(m\) 换成 \(kn\),这样就能过了。
可惜,当时没想到可以先对原图跑 MST 的来着,痛失 36pts+场切蓝题的机会。
首先,\(k=0\) 的点就是个最小生成树板子,板的不能再板的板子。我们直接跑 Kruskal 即可。预计得分 16pts。
然后说说我考场上的 64 分做法吧。
由于 \(k\) 很小,所以我们合理猜测复杂度带一个 \(O(2^k)\)。那这样的话,我们每次枚举要改造哪些乡村,对于每种情况,我们暴力地跑一次 Kruskal 求最小生成树,最终答案取min。
如果只是这么做的话,时间复杂度是 \(O(2^kM \log M)\) 的,其中 \(M=2m+2kn\)。(后来想了想,跑 Kruskal 为什么要建双向边)
好吧实际上 \(M=m+kn\)。
但是我们如果把边全部建好了以后,没必要每次枚举改造乡村时都排序的。所以我们一开始就排序,时间复杂度降为 \(O(M \log M + 2^kM)\)。
凭记忆复现出来的考场代码:(包括 \(k=0\) 和 \(k \le 5\) 两种情况)
考场代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e4+14;
const int M=1e6+6;
const int K=12;
const int inf=1e17;
int n,m,k,h[N+K],W[N+K],tot,fa[N+K];
struct sw{
int u,v,w,nxt;
}e[2*M+2*K*N];
inline void add(int u,int v,int w){
e[++tot]={u,v,w,h[u]};h[u]=tot;
}
inline bool check(int u,int v,int S){
//检查该边是否合法(或者说,查一查两个端点是否存在)
if(u<=n&&v<=n){
return 1;
}
else if(u>n&&v<=n){
int x=u-n-1;
if((S&(1<<x))){
return 1;
}
else{
return 0;
}
}
else{
int y=v-n-1;
if((S&(1<<y))){
return 1;
}
else{
return 0;
}
}
}
inline bool cmp(sw x,sw y){
return x.w<y.w;
}
inline int FIND(int x){
return (x==fa[x]?x:fa[x]=FIND(fa[x]));
}
inline int solve(int S){
//并查集初始化
for(int i=1;i<=n+k;i++){
fa[i]=i;
}
int res=0;
//累加点权
for(int i=1;i<=k;i++){
if((S&(1<<(i-1)))){
res+=W[i+n];
}
}
//正常Kruskal
for(int i=1;i<=tot;i++){
int u=e[i].u,v=e[i].v;
if(check(u,v,S)){
int fu=FIND(u),fv=FIND(v);
if(fu!=fv){
fa[fu]=fv;
res+=e[i].w;
}
}
}
return res;
}
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}
//其实我没必要写add函数的,跑Kruskal不需要这个
for(int i=1;i<=k;i++){
W[n+i]=read();
for(int j=1;j<=n;j++){
int w=read();
add(i+n,j,w);add(j,i+n,w);
}
}
sort(e+1,e+tot+1,cmp);
if(k==0){//16pts
for(int i=1;i<=n;i++){
fa[i]=i;
}
int ans=0;
for(int i=1;i<=tot;i++){
int u=e[i].u,v=e[i].v;
int fu=FIND(u),fv=FIND(v);
if(fu!=fv){
ans+=e[i].w;
fa[fu]=fv;
}
}
printf("%lld",ans);
}
else if(k<=10){//64pts
int ans=inf;
for(int S=0;S<(1<<k);S++){
ans=min(ans,solve(S));
}
printf("%lld",ans);
}
return 0;
}
接下来讲满分做法。
我们发现,原图里不在最小生成树的边是没有竞争力,也不会被算进答案里的。
因为它竞争不过那些在最小生成树里面的边,而它又不能作为连接改造乡村的边,或者我这么说,它在边少的情况下都竞争不过原本的城市边,现在要加入改造乡村的边,原本的生成树边都可能被淘汰掉,它更竞争不过了。
所以我们对原图跑 Kruskal,只保留最小生成树上的边,这样 \(M=kn\),然后进行 64 分的那个过程,足以通过民间数据了,时间复杂度是 \(O(2^kkn+kn \log kn+m \log m)\) 的,CCF换少爷机的话应该也能跑的过去。
代码:
P14362
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int x=0,f=1;char c=getchar();
while(c<48){
if(c=='-') f=-1;
c=getchar();
}
while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
const int N=1e4+14;
const int M=1e6+6;
const int K=12;
const long long inf=1e16;
int n,m,k,h[N+K],tot,W[N+K],fa[N+K];
struct Nahida{
int u,v,w;
}e[K*N],ee[M];
inline bool cmp(Nahida x,Nahida y){
return x.w<y.w;
}
inline int FIND(int x){
return (x==fa[x]?x:fa[x]=FIND(fa[x]));
}
inline bool check(int u,int v,int S){
if(u<=n&&v<=n){
return 1;
}
else if(u>n&&v<=n){
int x=u-n-1;
if((S&(1<<x))){
return 1;
}
else{
return 0;
}
}
else{
int y=v-n-1;
if((S&(1<<y))){
return 1;
}
else{
return 0;
}
}
}
inline long long solve(int S){
for(int i=1;i<=n+k;i++){
fa[i]=i;
}
long long res=0;
for(int i=1;i<=k;i++){
if((S&(1<<(i-1)))){
res+=W[i+n];
}
}
for(int i=1;i<=tot;i++){
int u=e[i].u,v=e[i].v;
if(check(u,v,S)){
int fu=FIND(u),fv=FIND(v);
if(fu!=fv){
fa[fu]=fv;
res+=e[i].w;
}
}
}
return res;
}
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++){
ee[i].u=read(),ee[i].v=read(),ee[i].w=read();
}
//对原图跑Kruskal
sort(ee+1,ee+m+1,cmp);
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
int u=ee[i].u,v=ee[i].v,w=ee[i].w;
int fu=FIND(u),fv=FIND(v);
if(fu!=fv){
e[++tot]={u,v,w};
fa[fv]=fu;
}
}
//然后仿照64分的过程来就好了
for(int i=1;i<=k;i++){
W[i+n]=read();
for(int j=1;j<=n;j++){
int w=read();
e[++tot]={i+n,j,w};
}
}
sort(e+1,e+tot+1,cmp);
long long ans=inf;
for(int S=0;S<(1<<k);S++){
ans=min(ans,1ll*solve(S));
}
printf("%lld",ans);
return 0;
}
其他事情等 CCF 结果出了再说吧,比如这东西能不能过官方数据什么的。以及 \(O(2^kn)\) 解法,本人暂时没有看明白,等我读明白了也会在这篇题解后面补充。

浙公网安备 33010602011771号