BZOJ2118: 墨墨的等式

来源:互联网 发布:multisim少数据库 编辑:程序博客网 时间:2024/04/30 02:18

题目大意:
给出一个关于许多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;}
0 0
原创粉丝点击