[洛谷 3403] 跳楼机(同余最短路)

题目

题目地址

题解

当出现形如“给定 \(n\) 个整数,求这 \(n\) 个整数能拼凑出多少的其他整数(\(n\) 个整数可以重复取)”,以及“给定 \(n\) 个整数,求这 \(n\) 个整数不能拼凑出的最小(最大)的整数”的问题时可以使用同余最短路的方法。

引自 OI-wiki。

不妨设 \(x < y < z\)。(为了减少状态)

\(dist[i]\) 为最小的 \(p=ay+bz\)\(p \equiv i \pmod {x}\)

则可以对于每个 \(i\),连一条 \(i->(i+y)\%x\) 的边,边权为 \(y\),连一条 \(i->(i+z)\%x\) 的的边,边权为 \(z\)。在以 \(1\) 为起点这个图上跑最短路,就可以求出 \(dist[i]\) 了。

注意 \(x,y,z \le 10^5\),所以 \(x\)\(y\) 的最小公倍数最大是 \(xy \le 10^{10}\) 不会爆 long long ,也就是说 \(dist[i]\) (如果不为 \(INF\) 的话)不会超过 \(10^{10}\)

求出 \(dist[i]\) 后,可以发现 \(dist[i]+kx,k \in Z\) 都可以到达(这就是设计同余状态的意义),于是就可以算出可以到多少个楼层了。

时间复杂度 \(O(n \log n)\)

代码

#include<bits/stdc++.h>
#define int long long
#define mp make_pair
#define fi first
#define se second
using namespace std;
inline int read() {
    int x = 0, f = 1; char ch = getchar();
    while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<3)+(x<<1)+(ch^48); ch=getchar(); }
    return x * f;
}
typedef pair<int,int> PII;
const int N = 1e5+7;
int h,x,y,z,cnt;
int head[N],dist[N];
struct Edge {
	int next,to,w;
}edge[N<<1];
inline void add(int u,int v,int w) {
	edge[++cnt] = (Edge)<%head[u],v,w%>;
	head[u] = cnt;
}
bool vis[N];
void Dijkstra() {
	memset(dist, -1, sizeof(dist));
	priority_queue<PII> q;
	dist[1] = 1;
	q.push(mp(-dist[1],1));
	while(!q.empty()) {
		int u = q.top().se;
		q.pop();
		if(vis[u]) continue;
		vis[u] = 1;
		for(int i=head[u];i;i=edge[i].next) {
			int v = edge[i].to, w = edge[i].w;
			if(dist[v]==-1 || dist[v]>dist[u]+w) {
				dist[v] = dist[u] + w;
				q.push(mp(-dist[v],v));
			}
		}
	}
}
signed main()
{
	h = read(), x = read(), y = read(), z = read();
	if(x > y) swap(x,y);
	if(x > z) swap(x,z);
	for(int i=0;i<x;++i) {
		add(i,(i+y)%x,y);
		add(i,(i+z)%x,z);
	}
	Dijkstra();
	int ans = 0;
	for(int i=0;i<x;++i) {
		if(dist[i] > -1) {
			if(h >= dist[i]) {
				ans += (h-dist[i])/x + 1;
			}
		}
	}
	printf("%lld\n",ans);
    return 0;
}
/*
15
4 7 9

9
*/

总结

利用同余设计状态。

同余最短路。

posted @ 2020-11-28 17:27  基地AI  阅读(143)  评论(0编辑  收藏  举报