Loading

同余最短路

概念

同余最短路是一种优化最短路建图的方法

通常是解决给定 \(m\) 个整数,求这 \(m\) 个整数能拼凑出多少的其他整数(这 \(m\) 个整数可以重复取)或给定 \(m\) 个整数,求这 \(m\) 个整数不能拼凑出的最小(最大)的整数。

栗题

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

题目大意:

对于 \(\sum_{i = 1}^na_ix_i = b\) ,给定 \(n\)\(a_{1…n}\)\(l\)\(r\) ,求有多少 \(b \in[l, r]\)​ 可以使得等式存在非负整数解

\(n\leq 12,0\leq a_i\leq5*10^5,0\leq l\leq r\leq 10^{12}\)

solution

把题目稍微变一下形

求有多少组 \(x_i\) ,使得 \(a_1*x_1 + a_2 * x_2…a_n*x_n = b\) 成立

发现这个题和货币系统那个题很像,考虑完全背包做法,但是这个题的 \(l, r\)​​ 很大,所以就有了同余最短路

好像题解解释的都不是很清楚 ??这用分组的思想感觉解释好像能解释的更清楚点 = =

对于这个题,我们可以先求出 \([1, l - 1]\)​​ 有多少 \(b\)​​ 满足条件,再求出 \([1, r]\)​​ 有多少 \(b\)​​​ 满足条件,作差就好了。

转换一下,与其考虑 \(b\) 是否满足条件,不如考虑 \(b\)​ 是否能被凑出来。

好,现在我们的目标就是统计 \([1,x]\)​​​​ 内有多少个数能被凑出来。

考虑这么一件事:找出 \(a_i\)​​ 中最小的记为 \(Min\)​​ ,令 \(p \in[1, Min)\)

我们所有的数按照对 \(Min\)​ 取模,按照余数分为 \(Min\)​​ 类。

如果 \(p + k*Min\)​​​​ 能够被凑出来,那么 \(p + (k + N^*) * Min\)​​​​ 也一定能被凑出来,也就是从这之后,这一类都能被凑出来了,令 \(tmp = p + k_{min} * Min\)​​ ,也就是这一类最早能被凑出来的数。如果 \(tmp \leq x\)​​​,那么这个区间关于这一类能凑出来的数的个数就是 \(\frac{x - tmp}{Min} + 1\)​​ ,求所有类就是对每一类数做相同操作加和就好了。

现在就只剩怎么求每一类被第一次凑出来的数是多少。

把它放在图里,让 \(p\)​​​ 向 \((p +a_i) \% Min\)​​​ 连一条权值为 \(a_i\)​​​ 有向边,也就是说凑出 \(p + a_i\)​​​ 的代价为 \(a_i\)​​​​ ,从 \(0\)​ 点到 \([0, Min)\)​ 的最短路长度就是每一类第一次被凑出来的数是多少。

然后就完成啦。

code

/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define int long long
#define rg register
using namespace std;
const int N = 5e5 + 5;
const int M = 6e6 + 5;
const int INF = 0x3f3f3f3f3f3f;
int read(){
    int x = 0,f = 1; char c = getchar();
    while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
    return x*f;
}
int n, l, r, a[N], m, Min = INF, dis[N], vis[N];
struct edge {int v, nxt, w;}e[M]; 
int head[N], E;
void add_edge(int u, int v, int w) {
   e[++E] = (edge) {v, head[u], w};
   head[u] = E;
}
queue<int> q;
void SPFA(int s) {
   memset(dis, INF, sizeof dis); 
   dis[s] = 0, q.push(s);
   while(!q.empty()) {
   	  int u = q.front(); q.pop(); vis[u] = 0;
   	      for (rg int i = head[u]; i; i = e[i].nxt){
   	        int v = e[i].v, w = e[i].w;
   	  	    if (dis[v] > dis[u] + e[i].w){	
			   dis[v] = dis[u] + e[i].w;
			   if (!vis[v]) q.push(v), vis[v] = 1;
		    }
	    }
   }
}
int query(int x) {
    int res = 0;
    for (rg int i = 0; i < Min; i++) 
       if (dis[i] <= x)
       	  res += (x - dis[i]) / Min + 1;
	return res;
}
signed main(){
   n = read(), l = read(), r = read();
   for (rg int i = 1, x; i <= n; i++) {
   	   x = read();
       if (x) a[++m] = x, Min = min(x, Min);//0 边没有贡献 
   }
   n = m;
   for (rg int i = 0; i < Min; i++) 
   	  for (rg int j = 1; j <= n; j++)
   	      if (a[j] != Min) add_edge(i, (i + a[j]) % Min, a[j]);
   SPFA(0);
   printf("%lld", query(r) - query(l - 1));
   puts("");
   return 0;
}

这道题的弱化版 P3403 跳楼机

注意这道题是从一楼开始,想想与上面板子比较哪里变了 = =

\(finish\)

posted @ 2021-07-23 17:19  Dita  阅读(70)  评论(0编辑  收藏  举报