Luogu | P2371 墨墨的等式 | 数论 / 图论 | 同余最短路


Description

墨墨突然对等式很感兴趣,他正在研究 $a_1x_1+a_2x_2+…+a_nx_n=B$ 存在非负整数解的条件,他要求你编写一个程序,给定 $N、{a_n}$、以及 $B$ 的取值范围,求出有多少 $B$ 可以使等式存在非负整数解。


Input Format

输入的第一行包含 $3$ 个正整数,分别表示 $N、B_{min}、B_{max}$ 分别表示数列的长度、$B$ 的下界、$B$ 的上界。

输入的第二行包含 $N$ 个整数,即数列 ${a_n}$ 的值。


Output Format

输出一个整数,表示有多少 $B$ 可以使等式存在非负整数解。


Input Sample

Sample 01

2 5 10
3 5


Output Sample

Sample 01

5


Data Range

$N \le 12$,$a_i \le 5 \times 10^5$,$1 \le B_{min} \le B_{max} \le 10^{12}$。


Solution

首先不难发现此题可以完全背包过部分数据。

将问题转化为求 $[0,B_{min}-1]$ 和 $[0,B_{max}]$ 两个区间的合法取值个数的差值。

设求 $[0,R]$ 中的合法取值。考虑将 $B$ 在同余类上分类讨论。设 $A$ 是 ${a_i}$ 中任意一个数,当原等式 $a_1x_1+a_2x_2+…+a_nx_n=B$ 成立时,设 $B \equiv x \mod A$,则原等式变为 $a_1x_1+a_2x_2+…+a_nx_n= kA + x$。在这里可以通过改变 $k$ 的值得到更多的取值。那么我们不妨找到一个最小的并且满足题目等式的 $kA + x$ 的值,之后在这个值的基础上变化 $k$ 即可。接下来放在同余类上讨论,若当前有某个值 $a_1x_1+a_2x_2+…+a_nx_n \mod A = y$,则考虑在此基础上加一个 $a_i$ 变为 $a_1x_1+a_2x_2+ … + a_i(x_i+1) + …+a_nx_n \equiv y+a_i \mod A$。转换为图论模型,即为从 $y$ 像 $y+a_i \mod A$ 连一条距离为 $a_i$ 的有向边。为 $\mod A$ 意义下的每个数都添加这些边,然后跑起点为 $0$ 的最短路,得到的 $dist_x$ 就是最小的并且满足题目等式的 $kA+x$。 这样,在 $\mod A = x$ 意义下能够变换的 $k$ 的数量即为 $\lfloor\frac{R-dist_x}{A}\rfloor + 1$ 个。枚举同余类 $x$ 统计答案即可。

实际操作时一般选最小的元素作为 $A$ 能使状态数最小,并且一定要忽略输入 $a_i$ 中的 0


Code

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>

#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
#define swap(x,y) {int t=x; x=y,y=t;}
#define wipe(x,y) memset(x,y,sizeof(x))
#define dbgIn(x) freopen(x".in","r+",stdin)
#define rep(x,y,z) for(int x=y,I=z;x<=I;++x)
#define dbgOut(x) freopen(x".out","w+",stdout)
#define file(x) freopen(x".in","r+",stdin); freopen(x".out","w+",stdout)

using std::priority_queue;

typedef long long ll;

inline void Read(ll &x){
    x=0; char ch=0; while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar(); return;
}

inline void Read(int &x){
    x=0; char ch=0; while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar(); return;
}

struct pt{
    int id;
    int dist;
    friend bool operator < (pt a,pt b){
        return a.dist>b.dist;
    }
};

struct edge{
    int from;
    int to;
    int next;
    int w;
}e[6000005];

int N;
int minn;
int node;

ll L;
ll R;

int src[15];
int first[500005];
int last[500005];

ll dist[500005];

bool done[500005];

priority_queue<pt>pq;

void addedge(int u,int v,int w){
    e[++node]=(edge){u,v,0,w};
    if(first[u]==0) first[u]=node;
    else e[last[u]].next=node;
    last[u]=node; return;
}

void Dijkstra(){
    wipe(dist,0x3f);
    dist[0]=0;
    pq.push((pt){0,0});
    while(!pq.empty()){
        pt x=pq.top(); pq.pop();
        if(done[x.id])
            continue;
        done[x.id]=1;
        for(int p=first[x.id];p;p=e[p].next){
            int y=e[p].to;
            if(dist[y]>dist[x.id]+e[p].w){
                dist[y]=dist[x.id]+e[p].w;
                pq.push((pt){y,dist[y]});
            }
        }
    }
    return;
}

ll GetVal(ll x){
    ll ret=0;
    rep(i,0,minn-1)
        if(dist[i]<=x)
            ret+=(x-dist[i])/minn+1;
    return ret;
}

int main(){
    Read(N);
    Read(L);
    Read(R);
    rep(i,1,N)
        Read(src[i]);
    minn=500005;
    rep(i,1,N)
        if(src[i])
            minn=min(minn,src[i]);
    rep(i,0,minn-1)
        rep(j,1,N)
            if(src[j])
                addedge(i,(i+src[j])%minn,src[j]);
    Dijkstra();
    printf("%lld\n",GetVal(R)-GetVal(L-1));
    return 0;
}