【洛谷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;
}
这篇博客是并行操作写完的 累死我了
放图


浙公网安备 33010602011771号