同余最短路的转圈法

学习自 Alex_Wei 的博客

同余最短路模板题:[国家集训队] 墨墨的等式

已知长为 n 的序列 a。对于不定方程 i=1naixi=b(xi0),问有多少 b[l,r] 可以使得方程有解。

n12ai5×105l,r1012

本文默认取模得到的结果都是自然数

首先把询问拆成区间 [0,l1][0,r],只考虑求区间 [0,l] 的合法数。

设集合 B={xZb=x}。显然如果 rB ,那么 r+aiB

设集合 Zpi={xZxi(modp)},即模 pi 的剩余类。显然有结论:

ij(modp)Zpi=Zpj

i=kk+p1Zpi=Z

我们随便选一个 ai,方便起见我们选择 a1。设 f(i) 表示 Za1iB 的最小元素,方便起见令 i[0,a1)

显然 k0,f(i)+ka1Za1iB。因此如果我们求出了 f(i),对于每个 i 我们都可以算出满足 Za1iB[0,l] 的大小,即模 a1i 的答案数。因为 i=0a11Za1i=Z,所以这些答案加起来不重不漏。

考虑如何求 fi,不难发现:

f(0)=0

f(i)=minj=2nf((iaj)moda1)+aj

一种方法是建图,用最短路解决。点数为 ai,边数为 nai,时间复杂度是 Dijkstra 的 O(na1logna1) 或 SPFA 的 O(na12)

但是还有另一种方式,考虑逐个添加 a,设 g(j,i) 表示只有前 j 个数时的 f(i),现在考虑从 j1j,添加一个 aj 会发生的转移:

g(j1,i)+kajg(j,(i+kaj)moda1)(k0)

(箭头表示“更新”)

对于每个 i,考虑从 0 开始枚举 k,不难发现 k=a1gcd(a1,aj) 时就可以停下了,因为此时 i+kaji(moda1),再往下走只会把之前更新过的再更新一遍,但是由于此时的 k 比上一次更新时大,所以这些更新不会再起效果。

由于模数相同时,不同的剩余类交集为空,所以考虑对于每个剩余类中的下标分别更新。形式化地说,就是对每个 x[0,aj),分别更新 fi,其中 iZajx。从完全背包的角度考虑这个问题,压掉第一维,得到转移 f(i)+ajf((i+aj)moda1)。从 i 开始走 k=a1gcd(a1,aj) 步可以更新完 i 能更新的点,那么从 i 开始走 2k 步,也就是“多走一圈”,就相当于从 i 所在的剩余类中的每个点都走了 k 步,也就可以更新完这个剩余类中的每个点。

for(int p = i, c = 0; c < 2; c += p == i) {
    gmin(f[(p + a[j]) % a[1]], f[p] + a[j]);
    p = (p + a[j]) % a[1];
}

每次可以更新 a1gcd(a1,aj) 个位置,所以只需要枚举前 gcd(a1,aj) 个位置进行更新即可。

时间复杂度 Θ(na1)。可以先排序让 a1 最小,减小常数。

#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define int long long
#define gmin(x, y) (x = min(x, y))
using namespace std;
constexpr int N = 15, A = 5e5;
int n, a[N], l, r, f[A];
signed main() {
#ifdef ONLINE_JUDGE
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
#endif
    cin >> n >> l >> r;
    rep(i, 1, n) cin >> a[i];
    sort(a + 1, a + n + 1);
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    rep(j, 2, n) rep(i, 0, __gcd(a[1], a[j]) - 1)
        for(int p = i, c = 0; c < 2; c += p == i) {
            gmin(f[(p + a[j]) % a[1]], f[p] + a[j]);
            p = (p + a[j]) % a[1];
        }
    int ans = 0;
    --l;
    rep(i, 0, a[1] - 1) {
        if(l >= f[i]) ans -= (l - f[i]) / a[1] + 1;
        if(r >= f[i]) ans += (r - f[i]) / a[1] + 1;
    }
    cout << ans << endl;
    return 0;
}
posted @   untitled0  阅读(98)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示