P2371 [国家集训队] 墨墨的等式

发布时间 2023-08-22 19:28:46作者: One_JuRuo

题目大意

对于等式 \(\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;
}