同余最短路学习笔记

上星期就想写了,一直没时间……

引入

同余最短路是用来解决用 $n$ 个整数,能拼凑成其他多少整数,最小能拼凑成多大整数,拼凑成一个模 P 意义下整数需要的最小拼凑次数。

同余最短路通过将问题每个整数上的答案转化为模 P 意义下的答案,在模 P 意义下进行计算,最终求出答案。

同余最短路

有点抽象,这里有道例题:洛谷 [P3403]

题目大意是有三个数 x,y,z,在 1 的基础上用这三个数凑出一个不超过 h 的数,求这个数的最大值。

如果定义状态 f(a) 表示 x 能否被凑出来,那么有

f(a+x)\leftarrow f(a)

f(a+y)\leftarrow f(a)

f(a+z)\leftarrow f(a)

但注意到 h\le 2^{63}-1,这样转移显然无法接受。

我们改设状态 g(a) 表示对于模 x 值为 a 的数,只用 y,z 能凑出的最小值,即 max_{i\%x=a,f(i)=1}\{i\}。对于求出来的 g(a),又可以用任意个 x 来凑出其他数。那么我们找到最大的 k,使得 g(a)+kx\le h,用g(a)+kx 更新答案,而 x\le 10^5,这样做是可行的。那么就有状态转移

g((a+y)\%x)\leftarrow g(a)

g((a+z)\%x)\leftarrow g(a)

如何解决它呢?我们可以对于每个 0\le a\le x-1 建边 (a,(a+y)\%x,y) 与 (a,(a+z)\%x,z),这样的话以结点 1 为起点到结点 a 的最短路就可表示 g(a) 的值,这个很好理解,就像差分约束建边一样。于是跑一边最短路结束。

另外,一般作为模数的那个数取所有数中最小的那个,这样可以保证剩余类尽可能地小。

Code:

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define lp(i, j, n) for(int i = j; i <= n; ++i)
#define dlp(i, n, j) for(int i = n; i >= j; --i)
#define mst(n, v) memset(n, v, sizeof(n))
#define mcy(n, v) memcpy(n, v, sizeof(v))
#define INF 1e18
#define MAX4 0x3f3f3f3f
#define MAX8 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int>
#define pll pair<ll, ll>
#define co(x) cerr << (x) << ' '
#define cod(x) cerr << (x) << endl
#define fi first
#define se second
#define eps 1e-8
#define lc(x) ((x) << 1)
#define rc(x) ((x) << 1 ^ 1)
#define pb(x) emplace_back(x)

using namespace std;

const int N = 100010;

namespace Gr {
    static const int M = N << 1;

    struct edge { int v, nxt; ll w; } E[M];
    int en, hd[N];

    void add(int u, int v, ll w) { E[++en] = { v, hd[u], w }, hd[u] = en; }

    struct node {
        int pos; ll dis;

        bool operator < (const node & t) const { return dis > t.dis; }
    };

    ll dis[N];
    bool vis[N];
    priority_queue<node> q;
    void dij() {
        mst(dis, 0x3f);
        dis[1] = 1, q.push({ 1, 1 });
        while(!q.empty()) {
            node fr = q.top(); q.pop();
            int u = fr.pos; ll d = fr.dis;
            if(vis[u]) continue;
            vis[u] = 1;
            for(int i = hd[u]; i; i = E[i].nxt) {
                int v = E[i].v; ll w = E[i].w;
                if(!vis[v] && d + w < dis[v]) dis[v] = d + w, q.push({ v, dis[v] });
            }
        }
    }
}
using namespace Gr;

ll H, x, y, z;

signed main() {
    // freopen(".in", "r", stdin);
    // freopen(".out", "w", stdout);
#ifndef READ
    ios::sync_with_stdio(false);
    cin.tie(0);
#endif
    cin >> H >> x >> y >> z;
    if(y < x) swap(x, y);
    if(z < x) swap(x, z);
    if(x == 1) return cout << H << endl, 0;
    lp(i, 0, x - 1) {
        add(i, (i + y) % x, y);
        add(i, (i + z) % x, z);
    }
    dij();
    ll ans = 0;
    lp(i, 0, x - 1) if(H >= dis[i]) ans += (H - dis[i]) / x + 1;
    cout << ans << endl;
    return 0;
}

还有一道题: CF [986F]

简要题意:

给定 n 与 k,问是否能将 n 分为若干个 k 的因数(11 除外)之和,每个因数都可以被选多次。

n\le 10^{18}k\le10^{15},最多 50 种不同的 k

一共 t 组询问,t\le 10^4

对于质因数个数为 1 的情况,判断是否 k 整除 n

对于质因数个数为 2 的情况,同样用同余最短路类似的思路,设这两个数为 p,q,那么有 qx\equiv n\, (\mathrm{mod}\,p),于是 x\equiv nq^{-1}\,(\mathrm{mod}\,p),算出 x 后,判断 qx 是否不大于 n 即可。

对于质因数个数大于 2 的情况,可以保证每个质因数大小不超过 10^5,显然分解出 k 的质因数然后直接同余最短路,思路类似。

对于每个不同的 k,求出答案即可,分解质因数可用 Pollard_Rho 快速分解。

Code:

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define lp(i, j, n) for(int i = j; i <= n; ++i)
#define dlp(i, n, j) for(int i = n; i >= j; --i)
#define mst(n, v) memset(n, v, sizeof(n))
#define mcy(n, v) memcpy(n, v, sizeof(v))
#define INF 1e18
#define MAX4 0x3f3f3f3f
#define MAX8 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int>
#define pll pair<ll, ll>
#define co(x) cerr << (x) << ' '
#define cod(x) cerr << (x) << endl
#define fi first
#define se second
#define eps 1e-8
#define lc(x) ((x) << 1)
#define rc(x) ((x) << 1 ^ 1)
#define pb(x) emplace_back(x)

using namespace std;

const int N = 60, M = 10010;

ll qmul(ll a, ll b, ll p) { return (a * b - (ll)((long double)a / p * b) * p + p) % p; }

ll qpow(ll x, ll k, ll p) {
    x %= p;
    ll res = 1;
    while(k) {
        if(k & 1) res = qmul(res, x, p);
        x = qmul(x, x, p), k >>= 1;
    }
    return res;
}

const int pcnt = 12;
const ll p0[pcnt + 1] = { 0,2,3,5,7,11,13,17,19,61,2333,4567,24251 };

bool miller_rabin(ll x, ll p) {
    if(qpow(x, p - 1, p) != 1) return false;
    ll k = p - 1;
    while(!(k & 1)) {
        k >>= 1;
        ll t = qpow(x, k, p);
        if(t != 1 && t != p - 1) return false;
        if(t == p - 1) return true;
    }
    return true;
}

bool test(ll p) {
    if(p == 1) return false;
    lp(i, 1, pcnt) if(p == p0[i]) return true;
    lp(i, 1, pcnt) if(!miller_rabin(p0[i], p)) return false;
    return true;
}

ll mgcd(ll a, ll b) { return a ? mgcd(b % a, a) : b; }

mt19937 rd(time(NULL));
vector<ll> c[N];

ll F(ll x, ll b, ll p) { return (qmul(x, x, p) + x + b) % p; }

void pollard_rho(int q, ll x) {
    // co("--"), co(q), co(x), cod(test(x));
    if(test(x)) return c[q].pb(x), void();
    while(true) {
        ll t1 = rd() % (x - 1) + 1, b = rd() % (x - 1) + 1, t2 = F(t1, b, x);
        int lim = 1;
        while(t1 != t2) {
            ll cnt = 1;
            lp(i, 1, lim) {
                ll tmp = qmul(cnt, abs(t1 - t2), x);
                if(!tmp) break;
                cnt = tmp;
                t1 = F(t1, b, x), t2 = F(F(t2, b, x), b, x);
            }
            ll t = mgcd(cnt, x);
            if(t != 1) return pollard_rho(q, t), pollard_rho(q, x / t), void();
            lim = min(lim << 1, 128);
        }
    }
}

void xmin(__int128 & x, __int128 & y, __int128 a, __int128 b) {
    __int128 t = mgcd(a, b), t1 = b / mgcd(a, b), t2 = a / mgcd(a, b);
    __int128 k = x / t1;
    x %= t1;
    if(x < 0) x += t1, k = k - 1;
    y += t2 * k;
}

namespace Gr {
    const int L = 3000010;

    struct edge { int v, nxt; ll w; } E[L];
    int en, hd[L];

    void add(int u, int v, ll w) { E[++en] = { v, hd[u], w }, hd[u] = en; }

    struct node {
        int pos; ll dis;

        bool operator <(const node & t) const { return dis > t.dis; }
    };

    ll dis[L];
    bool vis[L];
    priority_queue<node> q;
    void dij() {
        mst(dis, 0x3f), mst(vis, 0);
        dis[0] = 0, q.push({ 0, 0 });
        while(!q.empty()) {
            node fr = q.top(); q.pop(); int u = fr.pos;
            if(vis[u]) continue;
            vis[u] = 1;
            for(int i = hd[u]; i; i = E[i].nxt) {
                int v = E[i].v; ll w = E[i].w;
                if(!vis[v] && dis[u] + w < dis[v]) dis[v] = dis[u] + w, q.push({ v, dis[v] });
            }
        }
    }
}
using namespace Gr;

struct ques { ll n, k; } qs[M];

int T;
map<ll, int> ind;
vector<ll> ans[N];
bool iscalc[N];

signed main() {
    // freopen(".in", "r", stdin);
    // freopen(".out", "w", stdout);
#ifndef READ
    ios::sync_with_stdio(false);
    cin.tie(0);
#endif
    cin >> T;
    int cnt = 0;
    lp(i, 1, T) {
        cin >> qs[i].n >> qs[i].k;
        if(qs[i].k == 1) continue;
        if(ind.find(qs[i].k) == ind.end()) {
            ind[qs[i].k] = ++cnt;
            pollard_rho(cnt, qs[i].k);
        }
    }
    lp(i, 1, cnt) sort(c[i].begin(), c[i].end());
    // cod(c[1].size());
    // lp(i, 1, cnt) { for(auto t : c[i]) co(t); cod(""); }
    lp(i, 1, T) {
        if(qs[i].k == 1) { cout << "NO" << endl; continue; }
        int idx = ind[qs[i].k];
        if(c[idx].size() == 1) {
            cout << (qs[i].n % qs[i].k == 0 ? "YES" : "NO") << endl;
        }
        else if(c[idx].size() == 2) {
            __int128 a = c[idx][0], b = c[idx][1];
            ll res = qs[i].n % a * qpow(b % a, a - 2, a) % a;
            // co(x), cod(y);
            cout << (res * b <= qs[i].n ? "YES" : "NO") << endl;
        }
        else {
            if(iscalc[idx]) {
                cout << (ans[idx][qs[i].n % c[idx][0]] <= qs[i].n ? "YES" : "NO") << endl;
                continue;
            }
            mst(hd, 0), en = 0, mst(E, 0);
            lp(i, 1, c[idx].size() - 1) {
                lp(j, 0, c[idx][0] - 1) add(j, (j + c[idx][i]) % c[idx][0], c[idx][i]);
            }
            dij();
            cout << (dis[qs[i].n % c[idx][0]] <= qs[i].n ? "YES" : "NO") << endl;
            lp(i, 0, c[idx][0] - 1) ans[idx].pb(dis[i]);
            iscalc[idx] = 1;
        }
    }
    return 0;
}

写得有点草率,就这样吧先。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值