2021-02-18

codeup 问题 C: 最短路径

边权是二的整数幂,很大的数,int保存不可能,类似大整数保存下来,模拟加法操作和小于操作,因为是二进制数,操作比一般的大整数要简单,只是用的是稀疏表示,要注意有序。其实不用稀疏表示,也是可以的,因为总共就500个比特位,不多不少,开一个bool[500]的数组,表示一位是否存在,这样加法操作就是O(1)了。

开始的时候想错了,以为直接用指数代替幂进行Dijkstra就行了,最后记录路径再把真正的距离算出来。但是太天真了,错误的具体论证可以看注释,简单来说就是一些在以2的幂为权的图更优的路径,在以指数为权的图却是不优的。比如说我有两条路径,为了方便我只写它们的指数了:一条是1,2,3,一条是4,显然,1+2+3=6>4,在指数图,后者更优,但是在幂图,2+4+8=14<16,显然前者更优。所以很显然指数不能代替幂,这个想法过于Naive了。

#include <cstdio>
#include <queue>
#include <cassert>
#include <set>
using namespace std;

/* run this program using the console pauser or add your own getch, system("pause") or input loop */
const int MOD=100000;
const int INF=1e9;
int N, M;

#define MAXN 105

int Pow(int n) {
	// 计算 2^n MOD M.
	int ans=1;
	while (n--) {
		ans = (2*ans) % MOD;
	}
	return ans;
}

/*
二进制整数。
*/ 
struct Int {
	set<int,greater<int> > data; // 记录非零位,从大到小。
	typedef set<int,greater<int> >::iterator It;
	// 初始化为无穷大。 
	void Inf() {
		data.clear();
		data.insert(INF);
	}
	// 初始化为0. 
	void Zero() {
		data.clear();
	}
	friend bool operator<(const Int& a, const Int& b) {
		// 字典序比较各个非零位。
		It pa=a.data.begin();
		It pb=b.data.begin();
		while (pa!=a.data.end()&&pb!=b.data.end()) {
			if (*pa != *pb) {
				return *pa < *pb;
			}
			++pa;
			++pb;
		}
		return pa==a.data.end()&&pb!=b.data.end();
	}
	// 加入一位。 
	void Add(int b) {
		data.insert(b);
	}
	// 计算最终结果。 
	int change() {
		int ans=0;
		if (data.size()==1 && *data.begin() == INF) {
			return -1; // 不可达。 
		}
		for (It it=data.begin();it!=data.end();++it) {
			int p=Pow(*it);
			ans=(ans+p)%MOD;
		}
		return ans;
	}
};

// 邻接表用。 
struct Node {
	int v;
	int dis;
	Node(int _v, int _dis):v(_v),dis(_dis){
	}
};

// 堆用。 
struct Node2 {
	int v;
	Int dis;
	// dis为0. 
	Node2(int _v):v(_v){
		dis.Zero();
	}
	Node2(int _v, Int _dis):v(_v),dis(_dis){
	}
	friend bool operator<(const Node2& a, const Node2& b) {
		return b.dis < a.dis;
	}
};

vector<Node> Adj[MAXN];
Int d[MAXN];
bool vis[MAXN];

/*
不需要真的把边权设为2的幂,只有自然数即可,权值的大小关系是一样的,
但是求出来的距离就没用了,要记录路径,然后再计算真正的距离,记得取模。

这个算法不正确。
设想一个自然数数列An和一个自然数X,如果能从An的和小于X,推到出2^Ai的和小于2^X,并且也能
反之亦然,那么上述算法就是正确的。但是事实上,只能从An的和小于X推出2^Ai的和小于2^X,反之是不成立的。
即如果2^Ai的和小于2^X,那么Ai的和可能大于X。
事实上,由等比求和公式,0+2+...+2^(n-1)=2^n - 1 < 2^n ,而显然0+1+...+n-1 > n,所以两者的大小关系不能互相推导,
算法自然是错的。

正确的算法:
注意到路径的边权和,其实是2的幂次和,即2的多项式,并且每个项的系数只能取0或1(因为路径上没有重复的边,每条边的边权互异),
并且幂是稀疏的,所以可以用多项式表示,多项式可以求和和比较大小。 所以d数组的元素不是int,而是多项式。 
*/ 

void Dijkstra(int s) {
	for (int i=0;i<N;++i) {
		d[i].Inf();
		vis[i]=false;
	}
	d[s].Zero();
	priority_queue<Node2> Q;
	Q.push(Node2(s));
	while (!Q.empty()) {
		int u=Q.top().v;
		Q.pop();
		if (vis[u]) {
			continue;
		}
		vis[u]=true;
		for (int i=0;i<Adj[u].size();++i) {
			int v=Adj[u][i].v;
			// 2的幂保存为int。 
			int dis=Adj[u][i].dis;
			Int du=d[u];
			du.Add(dis);
			if (!vis[v] && du < d[v]) {
				d[v]=du;
				Q.push(Node2(v, du));
			}
		}
	}
}

/*
堆优化的Dijkstra,单源非负权最短路。
*/


int main(int argc, char** argv) {
	while (scanf("%d%d",&N,&M) != EOF) {
		fill(Adj,Adj+MAXN,vector<Node>());
		for (int i=0;i<M;++i) {
			int a,b;
			scanf("%d%d",&a,&b);
			int w=i;
			Adj[a].push_back(Node(b, w));
			Adj[b].push_back(Node(a, w));
		}
		Dijkstra(0);
		for (int i=1;i<N;++i) {
			int ans=d[i].change();
			printf("%d\n", ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值