【学习笔记】同余最短路

同余最短路是用来解决一类 ∑ i = 1 n a i x i ∈ [ L , R ] \sum_{i=1}^n a_ix_i\in[L,R] i=1naixi[L,R] 问题的方法。

其中 L , R L,R L,R 值非常大,而 n n n 不是很大,大概是接受 O ( n 2 ) O(n^2) O(n2) 的范围, x i x_i xi 是自定义的系数。

先差分一下,变成 ∑ i = 1 n a i x i ∈ [ 0 , R ] − ∑ i = 1 n a i x i ∈ [ 0 , L − 1 ] \sum_{i=1}^na_ix_i\in [0,R]-\sum_{i=1}^na_ix_i\in [0,L-1] i=1naixi[0,R]i=1naixi[0,L1]

我们选中其中 a i a_i ai 最小的作为标准 a 0 a_0 a0

显然,每个数都能表示成 x a 0 + r xa_0+r xa0+r 的形式, r r r 是余数 < a 0 <a_0 <a0

然后我们将所有的数按照 r r r 分类,分出了 a 0 a_0 a0 个类,编号 0 ∼ a 0 − 1 0\sim a_0-1 0a01

考虑当我们能够凑出一个数 t ∈ [ L , R ] t\in [L,R] t[L,R] 中,那么 t + a 0 , t + 2 a 0 , . . . t+a_0,t+2a_0,... t+a0,t+2a0,... 都能被凑出来。

且不难发现这些数都属于一个余数类中。

如果我们能求出每个余数类中最小被表示出来的数 t 0 t_0 t0,那么就可以用 ⌊ R − t 0 a 0 ⌋ + 1 \lfloor\frac{R-t_0}{a_0}\rfloor+1 a0Rt0+1 算出这个类中的 ∈ [ 0 , R ] \in[0,R] [0,R] 的合法数。

所以现在还需要快速求出每个类中的 t 0 t_0 t0

因为我们计算类中的个数就是无限制地用了 a 0 a_0 a0,所以不妨在这里就不再使用。即我们使用若干个除 a 0 a_0 a0 外的所有 a i a_i ai 来构造出。

假设我们能构造出某个余数类中的数 t t t,那么我们就能构造出编号为 ( t + a i ) m o d    a 0 (t+a_i)\mod a_0 (t+ai)moda0 余数类中的数 t + a i t+a_i t+ai

发现这个关系可以看作一条边,而整个过程我们无非是在求到一个点(余数类)的最短路(最小能表示出的数)。

具体而言,将每个余数类建成一个点,然后点 x x x ( x + a i ) m o d    a 0 (x+a_i)\mod a_0 (x+ai)moda0 点连边,边权为 a i a_i ai,然后求最短路。

0 0 0 点开始,初始化 dis ( 0 ) = 0 \text{dis}(0)=0 dis(0)=0 即可。因为最小的可以被构造出来的数肯定是 0 0 0,什么数都不用即可。

跳楼机

luogu-P3403

注意:楼层是从 1 1 1 开始的。我们整体往下移动一个单位即可。这题还不用差分,虽然差分没有任何难度。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define Pair pair < int, int >
vector < Pair > G[100005];
int h;
int a[5], dis[100005];
priority_queue < Pair, vector < Pair >, greater < Pair > > q;

void addedge( int u, int v, int w ) {
	G[u].push_back( make_pair( v, w ) );
}

void dijkstra() {
	q.push( make_pair( dis[0] = 0, 0 ) );
	while( ! q.empty() ) {
		int u = q.top().second, w = q.top().first; q.pop();
		if( dis[u] ^ w ) continue;
		for( int i = 0;i < G[u].size();i ++  ) {
			int v = G[u][i].first; w = G[u][i].second;
			if( dis[v] > dis[u] + w )
				q.push( make_pair( dis[v] = dis[u] + w, v ) );
		}
	}
}

signed main() {
	scanf( "%lld", &h );
	for( int i = 1;i <= 3;i ++ ) scanf( "%lld", &a[i] );
	sort( a + 1, a + 4 );
	if( a[1] == 1 ) return ! printf("%lld\n", h );
	for( int i = 0;i < a[1];i ++ ) {
		addedge( i, ( i + a[2] ) % a[1], a[2] );
		addedge( i, ( i + a[3] ) % a[1], a[3] );
	}
	memset( dis, 0x3f, sizeof( dis ) );
	dijkstra();
	int ans = 0;
	for( int i = 0;i < a[1];i ++ )
		if( dis[i] <= h - 1 )
			ans += ( h - 1 - dis[i] ) / a[1] + 1;
	printf( "%lld\n", ans ); 
	return 0;
}

[国家集训队]墨墨的等式

luogu-P2371

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define Pair pair < int, int >
int n, l, r;
int dis[500005], a[15];
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
vector < Pair > G[500005];

void addedge( int u, int v, int w ) {
	G[u].push_back( make_pair( v, w ) );
}

void dijkstra() {
	q.push( make_pair( dis[0] = 0, 0 ) );
	while( ! q.empty() ) {
		int u = q.top().second, w = q.top().first; q.pop();
		if( dis[u] ^ w ) continue;
		for( int i = 0;i < G[u].size();i ++  ) {
			int v = G[u][i].first; w = G[u][i].second;
			if( dis[v] > dis[u] + w )
				q.push( make_pair( dis[v] = dis[u] + w, v ) );
		}
	}
}

int query( int n ) {
	int ans = 0;
	for( int i = 0;i < a[1];i ++ )
		if( dis[i] <= n )
			ans += ( n - dis[i] ) / a[1] + 1;
	return ans;
}

signed main() {
	scanf( "%lld %lld %lld", &n, &l, &r );
	for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
	sort( a + 1, a + n + 1 );
	for( int i = 0;i < a[1];i ++ )
		for( int j = 2;j <= n;j ++ )
			addedge( i, ( i + a[j] ) % a[1], a[j] );
	memset( dis, 0x3f, sizeof( dis ) );
	dijkstra();
	printf( "%lld\n", query( r ) - query( l - 1 ) );
	return 0;
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值