P2371 [国家集训队] 墨墨的等式
题目大意
对于等式 \(\displaystyle\sum_{i=1}^{n}a_ix_i=b\) 求有多少 \(b\in [l,r]\) 使得等式存在非负数解。
思路
首先想到将区间 \([l,r]\) 分开,分为 \([0,l-1]\) 和 \([0,r]\) 再答案相减。
所以我们只需要能求得 \([0,x]\) 的答案即可。
若 \(\displaystyle\sum_{i=1}^{n}a_ix_i=t\),设其中一个 \(a_i\) 为 \(k\) ,那么 \(\displaystyle\sum_{i=1}^{n}a_ix_i=t+k\times l,l\in \mathbb{N}\)。发现 \(k\) 越小,\(b\) 可能的值越多,所以我们直接取最小的 \(a_i\) 为 \(k\)。
令 \(dis_i\) 代表 \(b\%k=i\) 时的最小值。
然后我们就可以连边 \(i->(a_j+i)\%k\),边权为 \(a_j\)。代表要从 \(i\) 变到 \((a_j+i)\) 需要加一个 \(a_j\)。因为 \(dis_i\) 的定义,所以需要模一遍 \(k\)。
其实在程序中,我们不需要建边,可以直接根据 \(a_j\) 计算更新即可。
跑一遍最短路后,我们发现对于答案模 \(k\) 是 \(i\) 的情况中,最小也是 \(dis_i\),若 \(dis_i\) 小于 \(x\) 则代表有解,且对于所有的 \(dis_i+k\times l,l\in \mathbb{N}\) 都是答案,所以在区间 \([0,x]\) 中满足情况的有 \(\lfloor \frac{x-dis_i}{k}\rfloor+1\) 个。
对于每个 \(dis_i\),我们只需要求得在 \([0,l-1]\) 和 \([0,r]\) 的解个数,然后相减就可以得到 \([l,r]\) 的解个数,再把每个 \(dis_i\) 求得的解个数加起来就是所有的可能值。
AC 代码
#include<bits/stdc++.h>
using namespace std;
const long long inf=0x3f3f3f3f3f3f3f3f;
long long n,l,r,a[20],k=inf,dis[500005],ans;
queue<int>q;
bool vis[500005];
inline void spfa(int s)//关于spfa,他没有死
{
memset(dis,0x3f,sizeof(dis)),dis[s]=0,q.push(s),vis[s]=1;//初始化
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=1;i<=n;++i)//不需要建图,根据定义直接找到链接的下一个点
{
int j=(u+a[i])%k;
if(dis[j]>dis[u]+a[i])
{
dis[j]=dis[u]+a[i];
if(!vis[j]) q.push(j);
vis[j]=1;
}
}
vis[u]=0;
}
}
int main()
{
scanf("%lld%lld%lld",&n,&l,&r);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]),k=min(k,a[i]);//读入的时候顺便找到最小的a[i]
spfa(0);
for(int i=0;i<k;++i) if(dis[i]!=inf) dis[i]-=i,dis[i]/=k,ans=ans-max(0ll,(l-1-i)/k-dis[i]+1)+max(0ll,(r-i)/k-dis[i]+1);//分别计算[0,l-1]和[0,r]的值,需要注意dis[i]小于x的情况,这种情况答案是0,所以需要max一遍
printf("%lld",ans);
return 0;
}