Y
K
N
U
F

同余最短路の学习

前言

好困的下午,反正没精神打题不如总结总结知识比较好。

正文

说白了其实就是最短路,不过在点和边的设计上很巧妙,多说理论实在枯燥,不如结合题目来说。

墨墨的等式

题意

给出 \(a_{1\dots n}\) , \(l\), \(r\)
求出 \([l,r]\) 中可以使 \(\sum^{n}_{i=1}{a_i x_i} = b\) 有非负整数解的 \(b\) 的个数。

其实容易想到多半就是求出小于等于 \(r\) 和 小于 \(l\) 的部分容斥出答案,问题便来到了如何求出这样的部分,也便用到了所谓同余最短路
先考虑 \(n\) 比较小的情况,比如 \(3\),则有 \(a_1 x_1 + a_2 x_2 + a_3 x_3 = b\),考虑这样子的式子的解的特点。注意到当等式两边同时对任意已有的 \(a_i\) 取模,则等式右侧结果不会因为 \(x_i\) 的改变而改变,这也就是建立了一个小小的模型。$$a_1 x_1 + a_2 x_2 + a_3 x_3 \mod a_1 = a_2 x_2 + a_3 x_3 = b \mod a_1$$ 考虑怎么把这个式子做成我们想要的可以拿来转移从而求出 \(b\) 的个数的东西,不妨继续向等式两边加入一些数,像这样:

\[a_2 (x_2 + 1) + a_3 x_3 = (b + a_2) \mod a_1 \]

容易得出只有发生了 \(x_{i\&\&i!=1}\) 的变化才会导致结果的变化,容易得到在没有超过 \(r\) 的上界范围内的每一个数 \(y\),都可以列出这样的转移式 \(y\mod a_1 -> (y + a_i) \mod a_1 \space\space cost = a_i\) 注意到这个式子显然可以当做最短路的转移,可以由此建出一个图来,在图上跑最短路,令 \(dis_{i}\) 为从 \(0\)(显然 \(dis_{0} = 0\))到 \(i\) 的最短路,这所谓最短路也就代表了令等号右边成为 \(i\) 消耗的最小代价,而 \((r - dis_i)\)(或\((l - dis_i)\)) 也就代表了剩余的可操作空间,让 \(dis_i\) 不变显然这一空间只能够塞 \(a_1\),能塞多少个也就是在 \(b % a_1 = dis_i\) 条件下解的个数,统计后作差即可。

顺带一提这种最短路很难卡死 spfa (不是不能,只是很难),兴许可以算是 spfa 的一次复活(?)

code:

#include <bits/extc++.h>
#define submit
#ifdef submit
    #define getchar getchar_unlocked
    #define putchar putchar_unlocked
#endif
#define e_ putchar(' ')
#define en_ putchar('\n')
using namespace std;
template <typename T> inline T in(T &n) {
    n = 0; char p = getchar();
    while(p < '-') p = getchar();
    bool f = p == '-' ? p = getchar() : 0;
    do n = (n << 1) + (n << 3) + (p ^ 48), p = getchar();
    while(isdigit(p));
    return n = f ? -n : n;
    return n;
}
template <typename T> inline void out(T x) {
    if(x < 0) putchar('-'), x = -x;
    if (x >= 10) out(x / 10);
    putchar(x % 10 + '0');
}
constexpr int N = 500000 + 10; const char me[] = "Ciallo~(∠・ω< )⌒ ★ ";
int a[14], n, l, r, dis[N], x = INT_MAX;
struct node {
    int to, w, nxt;
} e[N * 12];
int hd[N], tot;
inline void add(int u, int v, int w) {
    e[++tot] = {v, w, hd[u]};
    hd[u] = tot;
}
bool vis[N];
void dij(int s) {
    memset(dis, 63, (x + 2) * 8); // 科学的 memset 更好的效率!!! 强推!!!
    dis[s] = 0;
    priority_queue<pair<int, int> > q;
    q.push({0, s});
    int u;
    while(!q.empty()) {
        u = q.top().second; q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i = hd[u], v = e[i].to, w = e[i].w; i; i = e[i].nxt, v = e[i].to, w = e[i].w)
            if(dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                q.push({-dis[v], v});
            }
    }
}
struct {
    void excute(const char *I) {
        in(n), in(l), in(r);
        for(int i = 1; i <= n; i ++) { in(a[i]); if(a[i]) x = min(a[i], x);}// 注意虽然小点更好,但也不能太小(为0),那不合法
        for(int i = 0 ; i < x; i ++) {
            for(int j = 1; j <= n; j ++) {
                if(a[j] != x)
                    add(i, (a[j] + i) % x, a[j]); // 建图
            }
        }
        dij(0);
        int ans = 0;
        for(int i = 0; i < x; i ++) {
            if(dis[i] <= r) ans += (r - dis[i]) / x + 1;
            if(dis[i] < l) ans -= (l - 1 - dis[i]) / x + 1;
        }
        cout << ans << I;
    }
} world;
signed main() {
    world.excute(me);
}

[arc084_b]Small Multiple

题意

把这个拿出来是因为有一点点特殊,先把特殊点说了,这题转化完了可以是一个 01bfs 而并不止是 dij 或 spfa 之类的(再大点的数据范围也许可以卡掉最短路?)。然后咱来好好聊聊。
(如果你真的只看了我一个人上面说的东西的话)一个很难想到的点就是哪里来的同余?事实是这样的,我们不妨考虑一个数字的由来,如 \(114\),它是由 "1个100,1个10,4个1" 组成的数字,细想一下,也就是它主要由两种操作 —— \(n+1\)\(n*10\) 构成,也就是说,在不考虑进位的情况下,对于任意一个数字 \(x\) 都有着两种转移,\(x -> x+1 \space\space cost = 1\)\(x -> x*10 \space\space cost = 0\)、考虑 01bfs 利用双端队列始终维护队头的 \(cost_{sum}\) 为最小的,直到出现了一个 \(\mod k == 0\) 的数字就输出。注意到空间框框爆炸,又注意到在 \(\mod k\) 意义下得数不变,所以可以考虑使用高贵的 \(vis\) 数组解决这一切。

code:

#include <bits/extc++.h>
//#define submit
#ifdef submit
    #define getchar getchar_unlocked
    #define putchar putchar_unlocked
#endif
#define e_ putchar(' ')
#define en_ putchar('\n')
using namespace std;
template <typename T> inline T in(T &n) {
    n = 0; char p = getchar();
    while(p < '-') p = getchar();
    bool f = p == '-' ? p = getchar() : 0;
    do n = (n << 1) + (n << 3) + (p ^ 48), p = getchar();
    while(isdigit(p));
    return n = f ? -n : n;
    return n;
}
template <typename T> inline void out(T x) {
    if(x < 0) putchar('-'), x = -x;
    if (x >= 10) out(x / 10);
    putchar(x % 10 + '0');
}
constexpr int N = 1e6 + 10; const char me[] = "私の0721を見てください!";
typedef pair<double, int> node;
using lint = long long;
class {
  public:
    int k, ans, vis[N];
    deque<node> q;
    inline void excute(const char *I) {
        in(k);
        q.push_back({1, 1});
        vis[1] = 1;
        while(!q.empty()) {
            int u = q.front().first, w = q.front().second;
            q.pop_front();
            if(u == 0) {
                out(w); en_;
                return;
            }
            if(!vis[10 * u % k]) {
                q.push_front({10 * u % k, w});
                vis[10 * u % k] = 1;
            }
            if(!vis[u + 1]) {
                q.push_back({u + 1, w + 1});
            }
        }
    }
} world;
signed main() {
    world.excute(me);
}

总之先说到这里吧,更高深的应用我讲起来就不太好了(主要是懒),其实还有一个名为转圈的技巧想说,不过还是咕咕罢。。。。。

posted @ 2025-07-25 19:08  樓影沫瞬_Hz17  阅读(23)  评论(0)    收藏  举报