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条边,\(2^{500}\)太大了,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 @ 2022-04-30 23:03  带带绝缘体  阅读(37)  评论(0)    收藏  举报