[国家集训队]墨墨的等式(同余最短路)
前置题目(双倍经验):[洛谷P3403]跳楼机
题目
求\([l,r]\)中有多少个b可以使得方程\(\sum{a_ix_i = b}\)有非负整数解\((n\leq 12,l,r\leq 10^{12})\)
思路
问题等价于求\(solve(r)-solve(l-1)\)
考虑怎么求\(solve(r)\),设\(f_i\)表示\(x_1=0\)时(只用其他的数)能得到的最小的模\(a_1\)为\(i\)的数
显然,\(f_i\)可以通过加\(a_{2,3,...,n}\)变成转移给\(f_{(i+a_{2,3,4....n})\%a_1}\),将这个关系建边,跑一遍最短路即可得到\(f\)数组
由于\(f\)已经是不用\(a_1\)情况下可以得到的最小的值了,所以给\(f\)只加\(a_1\)就可以得到所有的合法情况;而根据\(f\)的定义,只在\(f_i\)上加\(a_1\)不会使得不同\(i\)出现\(f_i+a_1x_1\)相等的情况,所以分别处理每个\(f_i\)再加起来就行了
即\(ans=\sum{((r-f_i)/a_1 +1)}\)
P.S.注意特判存在\(a_i=1\)的情况;可以看出边数与\(a_1\)有关,取最小的\(a\)作为\(a_1\)(当然不取应该也没什么事)
Code
#include<bits/stdc++.h>
#define N 500005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,a[15];
ll l,r,dis[N];
bool ccf,vis[N];
struct Edge
{
int next,to,dis;
}edge[N*12];int head[N],cnt;
void add_edge(int from,int to,int dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}
template <class T>
void read(T &x)
{
char c; int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void dijkstra()
{
priority_queue< pair<ll,int> > q;
memset(dis,100,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[0]=0;
q.push(make_pair(-dis[0],0));
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
if(!vis[v]) q.push(make_pair(-dis[v],v));
}
}
}
}
int main()
{
read(n);read(l);read(r); --l;
for(int i=1;i<=n;++i) read(a[i]),ccf|=(a[i]==1);
if(ccf) { cout<<r-l<<endl; return 0; }
sort(a+1,a+n+1);
for(int i=0;i<a[1];++i)
for(int j=2;j<=n;++j)
add_edge(i,(i+a[j])%a[1],a[j]);
dijkstra();
ll ans=0;
for(int i=0;i<a[1];++i)
{
if(dis[i]<=r) ans+=(r-dis[i])/a[1]+1;
if(dis[i]<=l) ans-=(l-dis[i])/a[1]+1;
}
cout<<ans<<endl;
return 0;
}