题目大意:
给出一个关于许多x的不定方程:a1*x1+a2*x2+a3*x3+……..+an*xn=B,这里a都是非负整数,求在给定区间[l,r]内,使得所有x都为非负整数的,满足条件的B的个数。
这道怎么看都是数论题的题,其实可以用图论来解决,(额,好像有些突然)。先来分析一下吧,这道题弱机的我肯定是找不到什么优秀的数学方法来解决的,那么我就只好暴力枚举了(反正又不是我算。)直接枚举肯定是要T掉的,怎么办呢?这时我们会想到利用解决大数据、大规模的数学题时所用的方法——余数计算!
余数会满足大多数基本运算性质,还拥有许多优美的性质。枚举整个区间会超时,那么我们来枚举余数呢?恩,这好像是一个好思路。因为B是一个连续的区间,如果一个%a==k的数可以被我们“斗”出来,那么区间内所有%a==k的数都能被“斗”出来。
现在再针对这道题来看看,我们在这些a里任取一个ai,表示为cc,那么这个B%cc肯定是在0~cc-1之间的,如果一个B满足条件%cc==k,那么(B+cc)%cc也肯定==k,那其实就是说,只要我们能找到,%cc==k的,且满足条件的最小的B,在一直往上加cc,直到加到r为止,能有多少个B,(这些B都是符合条件的),就得到了B%cc==k所有的可能,再枚举不同的k,累加起来,就是0~r内全部可能的B值了。同理,0~l-1内所有可能的B值也可以求出,一减就是l~r内的可能了!
然后,为了使不同的余数k种类尽量少,所以ai(cc)尽量小就可以了,取a里面的最小值即可。
那么问题就转化为求%cc==k的B在非负范围之内最小的值是多少。我们不难发现,B==0为非负范围内最小的一个解,那么其他所有余数的最小B都可以从0这里加上若干个数而得到(a非负)。这样有一点最短路的感觉了吗?我们把0看做原点,我们可以在上面加上a1~aN中任意若干个数,相当于选择长度为a1~aN的路径(可重复)向外走,使得它走到一个%cc==k的距离最短。
然后,就可以用dis[k]保存%cc==k时最小的B,初始化dis[0]=0,通过加上不同的ai,得到新的余数,如果得到相同的余数k的话,可以用B较小的来更新dis[k],用spfa解决。
因为是用的stl里的queue,所以不用担心循环队列的问题。还有就是如果哪一个ai==0,对结果并没有影响,不用保存,因为cc是不能为0的。
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 500010;
int vis[N], n, a[N], cc;
LL dis[N], l, r, inf = 1e9;
void spfa(){
queue<int> qu;
qu.push(0);
vis[0] = 1;
dis[0] = 0;//0为其中最小的一个解
while(!qu.empty()){
int u = qu.front();
qu.pop();
vis[u] = 0;
for(int i=1; i<=n; i++){
int y = (u + a[i]) % cc;
if(dis[y] > dis[u] + a[i]){
dis[y] = dis[u] + a[i];
if(!vis[y]){
qu.push(y);
vis[y] = 1;
}
}
}
}
}
LL query(LL lim){//0~lim中B的和
LL ans = 0;
for(int i=0; i<cc; i++)
if(dis[i] <= lim) ans += (lim - dis[i]) / cc + 1;//因为有两端,所以是上限减下限除以公差加一
return ans;
}
int main(){
cc = (1 << 30) - 1;//cc用来保存a[1]~a[n]中最小的一个值
scanf("%d%lld%lld", &n, &l, &r);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
if(a[i] == 0){
i--,n--;//如果a==0,对结果毫无影响,直接删去
continue ;
}
cc = min(cc,a[i]);
}
for(int i=0; i<cc; i++)
dis[i] = 1e18;//要求最短路,初始化为最长
spfa();//dis[i]中存B的解中满足%cc==i最小的一个数
printf("%lld", query(r) - query(l-1));
return 0;
}