同余最短路(总结)

同余最短路

  • 能解决的问题: 给定 n n n个整数,求这 n n n个数能拼凑出多少其他的整数(可重复取)。或者求 n n n个整数不能拼出的最大/最小数。

这类题目较少,主要的就这两道题,看例题吧

P3403 跳楼机

  • 若暴力: O ( h ) O(h) O(h)

  • 若动态规划:完全背包

  • h h h到达 l o n g    l o n g long\;long longlong级别时,就不好用了,只能用同余最短路

  • 咋做:

    1. 先用同余思想将 O ( h ) O(h) O(h)复杂度降到 O ( m i n { a } ) O(min\{a\}) O(min{a}) 发现对于其中某一个 a i a_i ai(这里面指的是 x , y , z x,y,z x,y,z中的一个),我们考虑剩余 n − 1 n-1 n1个数求和可以组成的数 A = ∑ j = 1 , j ≠ i n − 1 k j a j A=\sum\limits_{j=1,j\ne i}^{n-1}k_ja_j A=j=1,j=in1kjaj。这个数若再加上 a i a_i ai,那么其组成的数的个数在 [ 1 , h ] [1,h] [1,h]范围内就有 ( h − A ) / a i + 1 (h-A)/a_i+1 (hA)/ai+1个。

      我们想这些数太过庞大而且有重复,那可不可以只考虑 A A A,有一个 A A A就有一堆个数了呢?考虑两个数 A p , A q A_p,A_q Ap,Aq,若 A P   m o d   a i = A q   m o d   a i A_P\bmod a_i=A_q\bmod a_i APmodai=Aqmodai,那么他俩统计的个数表示的数便是重复的,于是发现任意两个模 a i a_i ai相同的 A A A都会重复,那我们只找模 a i a_i ai不同的 a i a_i ai个数( 0 0 0 a i − 1 a_i-1 ai1,就是剩余系)不就可以了嘛。

    2. 和最短路的关系: 在找一个数由其他 a j ( j ≠ i ) a_j(j\ne i) aj(j=i)组成时,为了减少重复且大的数能由 A k A_k Ak推出,我们只需要找最小的 A k A_k Ak使 A k   m o d   a i = k A_k\bmod a_i=k Akmodai=k即可。

      我们设 f ( k ) f(k) f(k)表示在 a i a_i ai的剩余系内到达的 A k A_k Ak,由动态规划思想发现
      f ( ( k + a j ) % a i ) = min ⁡ { f ( k ) + a j } f((k+a_j)\%a_i)=\min\{f(k)+a_j\} f((k+aj)%ai)=min{f(k)+aj}
      化作最短路就表示一个节点 f ( k ) f(k) f(k)到另一个节点 f ( ( k + a j ) % a i ) f((k+a_j)\%a_i) f((k+aj)%ai)的一条边权为 a j a_j aj的路径,而且求的是最小值最短路)。

    3. 最后用最短路算法跑一遍,将所有代表 a i a_i ai的剩余系的节点的最小值算出就行了

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define db double
#define VI vector<int>
#define pi acos(-1.0)
#define PLI pair<ll, int>
const int INF = 0x7fffffff;
const int N = 1e5 + 5;
const db eps = 1e-10;
using namespace std;
int a[5];
bool vis[N];
ll ans = 0, h, dist[N];  //dist[i]表示仅通过操作2和操作3能到达的 mod x == i 的最小楼层: **可能为 ll** 
void dij(int k){
    priority_queue<PLI, vector<PLI>, greater<PLI> > q;
    memset(dist, 0x3f, sizeof(dist));
    memset(vis, 0, sizeof(vis));
    dist[k] = 1, q.push({dist[k], 1});  //从第 1 层开始
    while(q.size()){
        PLI now = q.top(); q.pop();
        int nod = now.second;
        if(vis[nod]) continue;
        vis[nod] = 1;
        rep(i, 2, 3){
            int To = (dist[nod] + a[i]) % a[1];  //下一结点
            if(dist[To] > dist[nod] + a[i]){
                dist[To] = dist[nod] + a[i];
                q.push({dist[To], To});
            }
        }
    }
}
int main(){
    cin >> h;
    rep(i, 1, 3) cin >> a[i];
    sort(a + 1, a + 4);
    if(a[1] == 1){
        cout << h << endl;
        return 0;
    }
    dij(1);
    rep(i, 0, a[1] - 1)
        if(dist[i] <= h) ans += (h - dist[i]) / a[1] + 1;  //统计个数
    cout << ans;
}

P2371墨墨的等式

  • 就是把三个数变成了 n n n个数
  • 最后也区间也不是 [ 1 , h ] [1,h] [1,h],而是 [ l , r ] [l,r] [l,r],就要像前缀和一样减一下前面的。
//同余最短路
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define ll long long
#define PLI pair<ll, int>
#define pi acos(-1.0)
const int INF = 0x7fffffff;
const int N = 5e5 + 5;
using namespace std;
int n, a[N], vis[N];
ll l, r, dist[N];
void dij(){
    memset(dist, 0x3f, sizeof(dist));
    memset(vis, 0, sizeof(vis));
    priority_queue<PLI, vector<PLI>, greater<PLI> > heap;
    dist[0] = 0, heap.push({0, 0});  //加入初始节点
    while(heap.size()){
        PLI now = heap.top(); heap.pop();
        int nod = now.second;
        if(vis[nod]) continue;
        vis[nod] = 1;
        rep(i, 2, n){
            int To = (dist[nod] + a[i]) % a[1];
            if(dist[To] > dist[nod] + a[i]){
                dist[To] = dist[nod] + a[i];
                heap.push({dist[To], To});
            }
        }
    }
}
int main(){
    cin >> n >> l >> r;
    rep(i, 1, n) scanf("%d", &a[i]);
    sort(a + 1, a + 1 + n);
    dij();
    ll ans = 0;
    rep(i, 0, a[1] - 1){  //[1, r] - [1, l - 1]的区间
        if(dist[i] <= r){
            ans += (r - dist[i]) / a[1] + 1;
            if(dist[i] < l) ans -= (l - 1 - dist[i]) / a[1] + 1;  
        }
    }
    cout << ans << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值