acwing1050

题目:
N 个城市,标号从 0 到 N−1,M 条道路,第 K 条道路(K 从 0 开始)的长度为 2K,求编号为 0 的城市到其他城市的最短距离。

输入格式
第一行两个正整数 N,M,表示有 N 个城市,M 条道路。

接下来 M 行两个整数,表示相连的两个城市的编号。

输出格式
N−1 行,表示 0 号城市到其他城市的最短路,如果无法到达,输出 −1,数值太大的以 mod100000 的结果输出。

数据范围
2≤N≤100,
1≤M≤500
输入样例:
4 4
1 2
2 3
1 3
0 1
输出样例:
8
9
11



思路:
本鼠本来想找个最小生成树的题做,没想到在acwing找到了这道题,完全没用到最小生成树的知识,笑的。
这道题比pat甲级的题恶心了很多,本鼠一开始没考虑周全,直接用SPFA做,挂掉了70%的数据,经过抄袭其他博客才弄明白怎么做。
首先分析题目,每条边的权重是2的幂次,这个可以提醒我们,对第i条边,它的权重大于前i-1条边的权重和;同时最多有500条边,2500太大了,long long都存不下,所以要取模。但是取模后边的权值大小关系可能会发生变化,比如9999与10000同时对10000取模,后者的结果远小于前者,这样处理后直接求最短路径无疑会带来错误。
考虑到对第i条边,它的权重大于前i-1条边的权重和这个启发性的结论,我们不妨这样考虑:如果新输入的边的两个节点在一个已经连通的分量中,那么这条边一定不可能成为最短路径树上的边。于是我们使用并查集,边读取输入边构建union。对于将两个union连接到一起的边,它必定是最短路径树的一条边,那么就将这条边加入邻接表;若一条边的两个节点已经在同一个union中,那么就将这条边丢弃。经过这样处理后,我们就可以得到一个最短路径树(森林)。剩下的就是遍历0号节点所在的树,求最短距离。这一步用dijkstra,SPFA,DFS都可以。
求解边权时,要使用快速幂算法,在算法中每步都要对100000取模。

代码1:

#include<iostream>
#include<vector>
#include<cstring>
#include<cstdio>
#include<queue>
#include <limits.h>
using namespace std;
#define ll long long
//用于邻接表
struct node {
int id;
ll w;
node(int x,ll y):id(x),w(y){}
};
const int MAX = 105;
const ll INF = LLONG_MAX;
const int MOD = 100000;
int n, m;
//邻接表
vector<node> adj[MAX];
//判别节点是否在队列中
bool inq[MAX] = { false };
ll disto[MAX];
//并查集
int uf[MAX] = { 0 };
//储存两个集合的根节点编号
int root1, root2;
//判别并查集中a和b是否在同一个union中
bool connect(int a, int b) {
while (a != uf[a]) a = uf[a];
while (b != uf[b]) b = uf[b];
root1 = a;
root2 = b;
return a == b;
}
//快速幂,求(a^b)%MOD
ll qmi(ll a, ll b) {
ll ans = 1, base = a;
while (b != 0) {
if ((b & 1) != 0)
ans = ans * base % MOD;
base = base * base % MOD;
b = b >> 1;
}
return ans;
}
void input() {
cin >> n >> m;
//初始化并查集
for (int i = 0; i < n; i++)
uf[i] = i;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
if (connect(u, v))
continue;
//将两个集合合并
uf[root2] = root1;
ll w = qmi(2,i);
adj[u].push_back(node(v, w));
adj[v].push_back(node(u, w));
}
}
void SPFA() {
disto[0] = 0;
queue<int> q;
q.push(0);
inq[0] = true;
while (!q.empty()) {
int u=q.front();
q.pop();
inq[u] = false;
for (int i = 0; i < adj[u].size(); i++) {
int v = adj[u][i].id;
ll w = adj[u][i].w;
//松弛边
if (disto[v] > disto[u] + w) {
disto[v] = disto[u] + w;
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
}
}
int main(void) {
input();
fill(disto, disto + MAX, INF);
SPFA();
for (int i = 1; i < n; i++) {
if (disto[i] == INF)
cout << "-1" << endl;
else
cout << disto[i]% MOD << endl;
}
return 0;
}

代码2:

#include<iostream>
#include<vector>
#include<cstring>
#include<cstdio>
#include<queue>
#include <limits.h>
using namespace std;
#define ll long long
//用于邻接表
struct node {
int id;
ll w;
node(int x,ll y):id(x),w(y){}
};
const int MAX = 105;
const ll INF = LLONG_MAX;
const int MOD = 100000;
int n, m;
//邻接表
vector<node> adj[MAX];
//判别节点是否在队列中
bool inq[MAX] = { false };
ll disto[MAX];
//并查集
int uf[MAX] = { 0 };
//用于DFS
bool visit[MAX] = { false };
//储存两个集合的根节点编号
int root1, root2;
//判别并查集中a和b是否在同一个union中
bool connect(int a, int b) {
while (a != uf[a]) a = uf[a];
while (b != uf[b]) b = uf[b];
root1 = a;
root2 = b;
return a == b;
}
//快速幂,求(a^b)%MOD
ll qmi(ll a, ll b) {
ll ans = 1, base = a;
while (b != 0) {
if ((b & 1) != 0)
ans = ans * base % MOD;
base = base * base % MOD;
b = b >> 1;
}
return ans;
}
void input() {
cin >> n >> m;
//初始化并查集
for (int i = 0; i < n; i++)
uf[i] = i;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
if (connect(u, v))
continue;
//将两个集合合并
uf[root2] = root1;
ll w = qmi(2,i);
adj[u].push_back(node(v, w));
adj[v].push_back(node(u, w));
}
}
//用DFS求解
void DFS(int id,ll dist) {
visit[id] = true;
for(int i = 0; i < adj[id].size(); i++) {
int v = adj[id][i].id;
int w = adj[id][i].w;
if (!visit[v]) {
disto[v] = dist + w;
DFS(v,disto[v]);
}
}
}
int main(void) {
input();
fill(disto, disto + MAX, INF);
DFS(0,0);
for (int i = 1; i < n; i++) {
if (disto[i] == INF)
cout << "-1" << endl;
else
cout << disto[i]% MOD << endl;
}
return 0;
}
posted @   带带绝缘体  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示