【洛谷P10928 P10929】走廊泼水节 黑暗城堡

今天做了书上例题的代码实现

两道绿题都很有意思:
先看第一道:

P10928 走廊泼水节

题目描述

给定一棵 \(N\) 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。

求增加的边的权值总和最小是多少。

注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。

输入格式

第一行包含整数 \(t\),表示共有 \(t\) 组测试数据。

对于每组测试数据,第一行包含整数 \(N\)

接下来 \(N-1\) 行,每行三个整数 \(X,Y,Z\),表示 \(X\) 节点与 \(Y\) 节点之间存在一条边,长度为 \(Z\)

输出格式

每组数据输出一个整数,表示权值总和最小值。

每个结果占一行。

输入输出样例 #1

输入 #1

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5

输出 #1

4
17

说明/提示

数据保证,\(1\leq t\leq 10\)\(1 \le N \le 6000\)\(1 \le Z \le 100\)

解法&&个人感想

这里书上的意思是给出了一个需要牢记的推论:
给定一张无向图G=(V,E),n=|V|,m=|E|,从E中选出k<n-1条边构成G的一个生成森林,若再从剩余的m-k条边种选n-1-k条边添加到生成森林中,使其成为G的生成树,并且选出的边的权值之和最小,则该生成树一定包含着(m-k)条边种连接生成森林的两个不连通节点的权值最小的边。
可能没怎么看懂?
我们用实例解释一下
我们可以用Kruscal来解释 假如一个联通块(当然是树状的,因为是生成树嘛)要和另一个联通块连接,Kruscal基于这个推论,取出两个联通块之间权值最小的那条边进行连接。
那么,要满足添加的边权值最小,怎么办?
就是 这两个联通块之间的边,除了权值最小的那条边,其他边的权值全部为最小那条边的权值加一
所以,我们在进行Kruscal跑联通块的时候,只需记录每个联通块的大小,并在路径压缩的时候更新
由乘法原理,更新的时候添加的边的条数是两个联通块点的数量之积-1
另外,此题多测记得清空
下面看代码:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxm 5000005
#define maxn 6005
using namespace std;
struct Edge{
    int from,to,w;
};
Edge edge[maxm];
int fa[maxn];
int siz[maxn];
int t;
int n,x,y,z;
ll ans=0;
bool cmp(Edge x,Edge y){
    return x.w<y.w;
}
int get(int x){
    if(fa[x]==x) return x;
    return fa[x]=get(fa[x]);
}
void merge(int x,int y){
    fa[x]=y;
    siz[y]+=siz[x];
    return ;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        ans=0;
        for(int i=1;i<=n;i++){
            fa[i]=i;
            siz[i]=1;
        }
        for(int i=1;i<=n-1;i++){
            scanf("%d%d%d",&x,&y,&z);
            edge[i].from=x;
            edge[i].to=y;
            edge[i].w=z;
        }
        sort(edge+1,edge+n,cmp);
        for(int i=1;i<=n-1;i++){
            int x=get(edge[i].from),y=get(edge[i].to);
            int z=edge[i].w;
            if(x==y) continue;
            ans+=((ll)siz[x]*(ll)siz[y]-1)*(ll)(z+1);
            merge(x,y);
        }
        printf("%lld\n",ans);
    }
    system("pause");
    return 0;
}

下面我们看第二题:

P10929 黑暗城堡

题目描述

在顺利攻破 Lord lsp 的防线之后,lqr 一行人来到了 Lord lsp 的城堡下方。

Lord lsp 黑化之后虽然拥有了强大的超能力,能够用意念力制造建筑物,但是智商水平却没怎么增加。

现在 lqr 已经搞清楚黑暗城堡有 \(N\) 个房间,\(M\) 条可以制造的双向通道,以及每条通道的长度。

lqr 深知 Lord lsp 的想法,为了避免每次都要琢磨两个房间之间的最短路径,Lord lsp 一定会把城堡修建成树形的。

但是,为了尽量提高自己的移动效率,Lord lsp 一定会使得城堡满足下面的条件:

\(D[i]\) 为如果所有的通道都被修建,第 \(i\) 号房间与第 \(1\) 号房间的最短路径长度;而 \(S[i]\) 为实际修建的树形城堡中第 \(i\) 号房间与第 \(1\) 号房间的路径长度;要求对于所有整数 \(i\),有 \(S[i]=D[i]\) 成立。

为了打败 Lord lsp,lqr 想知道有多少种不同的城堡修建方案。

保证至少存在一种可行的城堡修建方案。

你需要输出答案对 \(2^{31}–1\) 取模之后的结果。

输入格式

第一行有两个整数 \(N\)\(M\)

之后 \(M\) 行,每行三个整数 \(X,Y\)\(L\),表示可以修建 \(X\)\(Y\) 之间的一条长度为 \(L\) 的通道。

输出格式

一个整数,表示答案对 \(2^{31}–1\) 取模之后的结果。

输入输出样例 #1

输入 #1

3 3
1 2 2
1 3 1
2 3 1

输出 #1

2

说明/提示

数据保证,\(2 \le N \le 1000\)\(N-1 \le M \le N(N-1)/2\)\(1 \le L \le 100\)

解法&&个人感想

这题其实是个板子 叫做所谓的“最短路径生成树”
也就是从1开始跑一次dijkstra 对于任意边i->j 有d[j]=d[i]+edge(i,j)这样的树
叫做最短路径生成树
啊是的我们需要跑一遍Dijkstra
然后通过某种手段 求出每个节点的可能数sum 最后由乘法原理乘起来
然后我试了两种存储数据的方法,链式前向星和邻接表(vector实现)
这两种方法得出sum的数据
链式前向星的话因为没法用O($ n^2 \()的时间复杂度遍历,因此我们需要在跑Dijkstra的时候更新,有点像“最短路计数”那道题 邻接表则是跑完Dijkstra之后遍历O(\) n^2 $)更新 比较直观
最后注意取模
下面看代码:
链式前向星:

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 1005
#define maxm 3000005
#define INF 2147483647
using namespace std;
struct Edge{
    int from,to,nex,w;
};
Edge edge[maxm];
int d[maxn];
int n,m;
int vis[maxn];
int x,y,z,tot;
int head[maxm];
priority_queue<pair<int,int>>q;
ll ans=1;
int sum[maxn];
void add(int x,int y,int z){
    edge[++tot].to=y;
    edge[tot].from=x;
    edge[tot].w=z;
    edge[tot].nex=head[x];
    head[x]=tot;
}
void dijkstra(int sx){
    memset(d,0x3f,sizeof(d));
    memset(vis,0,sizeof(vis));
    d[sx]=0;
    q.push(make_pair(0,sx));
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].nex){
            int y=edge[i].to,z=edge[i].w;
            if(d[y]>d[x]+z){
                d[y]=d[x]+z;
                q.push(make_pair(-d[y],y));
                sum[y]=0;
            }
            if(d[y]==d[x]+z){
                sum[y]+=1;
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    dijkstra(1);
    for(int i=2;i<=n;i++){
        ans=ans*sum[i]%INF;
    }
    printf("%lld\n",ans);
    system("pause");
    return 0;
}

邻接表:
刚好复习了一下构造函数()

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define maxn 1005
#define INF 2147483647
using namespace std;
int n,m;
int x,y,z;
struct Edge{
    int to,w;
    Edge(int a,int b){
        to=a,w=b;
    }
};
vector<Edge>edge[maxn];
int vis[maxn];
int d[maxn];
int ma[maxn][maxn];
priority_queue<pair<int,int>>q;
ll ans=1;
void dijkstra(int sx){
    memset(d,0x3f,sizeof(d));
    memset(vis,0,sizeof(vis));
    d[sx]=0;
    q.push(make_pair(0,sx));
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=0;i<edge[x].size();i++){
            int y=edge[x][i].to,z=edge[x][i].w;
            if(d[y]>d[x]+z){
                d[y]=d[x]+z;
                q.push(make_pair(-d[y],y));
            }
        }
    }
}
int main(){
    memset(ma,0x3f,sizeof(ma));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        edge[x].push_back(Edge(y,z));
        edge[y].push_back(Edge(x,z));
        ma[x][y]=min(ma[x][y],z);
        ma[y][x]=min(ma[y][x],z);
    }
    dijkstra(1);
    for(int i=2;i<=n;i++){
        int sum=0;
        for(int j=1;j<=n;j++){
            if(d[i]==d[j]+ma[i][j]) sum++;
        }
        if(sum) ans=(ans*sum)%INF;
    }
    printf("%lld\n",ans);
    system("pause");
    return 0;
}

这篇博客是并行操作写完的 累死我了
放图
image

posted @ 2025-04-08 16:53  elainafan  阅读(27)  评论(0)    收藏  举报