BZOJ2118 由数论推导至图论!最短路SPFA

本文介绍了如何使用图论中的SPFA算法解决一道数论问题,即找出不定方程中非负整数解的个数。通过分析不定方程的性质,将问题转化为寻找特定余数范围内满足条件的B值,利用SPFA算法进行求解,最终给出完整的代码实现。
摘要由CSDN通过智能技术生成

吾王镇楼
世界真的很大
今天上午pty大神讲图论,在应用章节莫名其妙地抛出了这道神题,题目是这样的:给出一个不定方程:a1*x1+a2*x2+a3*x3+……..+an*xn=B,这里a都是常数,x是未知数,求在给定区间l,r内,使得所有x都为非负整数的,满足条件的B的个数。
这道怎么看都是数论题的题,其实可以用图论来解决,我反正死活没想出来。。先来分析一下吧,我们在这些a里任取一个ai,表示为k,那么这个B%k肯定是在0–k-1之间的,如果一个B满足条件,这个B%k=d,那么(B+k)%k也肯定为d,那其实就是说,只要我们能找到,%k=d的,且满足条件的最小的B,在一直往上加k,直到加到r为止,能有多少个B,(这些B都是符合条件的),就得到了B%k=d所有的可能,在枚举不同的d,累加起来,不就是0–r内全部可能的B值了嘛。同理,0–l-1内所有可能的B值也可以求出,一减不就是l–r内的可能,不就是答案呐!
然后,为了使不同的余数d种类尽量少,所以k尽量小就可以了,取a里面的最小值即可,代码:

for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i] == 0)
        {
            i--,n--;
            continue ;
        }
        small=min(small,a[i]);
    }

这里的small就是上文的k,还有就是如果哪一个a是0,就相当于没有这一项,就不用保存,当然small是不能为0的;
然后,之所以可以用spfa,就是因为要求余d时最小的B,用dis[d]保存,首先队首是0,因为余0时,B为0是肯定可以且最小的(非负),所以dis[0]=0,通过加上不同的a,得到新的余数,如果得到相同的余数的话,可以用B较小的来更新did值,或者说“松弛”,怎么样,spfa吧?代码:

void spfa()
{
    queue<int> state;
    state.push(0);
    vis[0]=1;
    dis[0]=0;
    while(!state.empty())
    {
        int u=state.front();
        state.pop();
        vis[u]=0;
        for(int i=1;i<=n;i++)
        {
            int y=(u+a[i])%small;
            if(dis[y]>dis[u]+a[i])
            {
                dis[y]=dis[u]+a[i];
                if(!vis[y])
                {
                    state.push(y);
                    vis[y]=1;
                }
            }
        }
    }
}

看起来不错,因为是用的stl里的queue,所以不用担心循环队列的问题。
好,上完整代码了:

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long dnt;
int vis[500010],n,a[500010],small;
dnt dis[500010],l,r,inf=1e9;
void spfa()
{
    queue<int> state;
    state.push(0);
    vis[0]=1;
    dis[0]=0;
    while(!state.empty())
    {
        int u=state.front();
        state.pop();
        vis[u]=0;
        for(int i=1;i<=n;i++)
        {
            int y=(u+a[i])%small;
            if(dis[y]>dis[u]+a[i])
            {
                dis[y]=dis[u]+a[i];
                if(!vis[y])
                {
                    state.push(y);
                    vis[y]=1;
                }
            }
        }
    }
}

dnt query(dnt k)
{
    dnt ans=0;
    for(int i=0;i<small;i++)
       if(dis[i]<=k) ans+=(k-dis[i])/small+1;
    return ans;
}

int main()
{
    small=(1<<30)-1;
    scanf("%d%lld%lld",&n,&l,&r);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i] == 0)
        {
            i--,n--;
            continue ;
        }
        small=min(small,a[i]);
    }

    for(int i=0;i<small;i++)
       dis[i]=100000000000000000LL;
    spfa();
    printf("%lld",query(r)-query(l-1));
    return 0;
}

嗯,就是这样;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值