题目大意: 在 1 1 1 ~ h h h 范围内,有多少个数能够拆分成 x , y , z x,y,z x,y,z 的和。
题解
同余最短路模板题。
考虑在模 x x x 意义下, y , z y,z y,z 能够组成哪些数,设 f [ i ] f[i] f[i] 表示用 y , z y,z y,z 能组成的最小的 模 x x x 等于 i i i 的数。
显然有这样的转移: f [ ( i + y ) m o d x ] = f [ i ] + y f[(i+y)\bmod x]=f[i]+y f[(i+y)modx]=f[i]+y, z z z 类似,发现这个转移很像最短路的转移: f [ y ] = f [ x ] + c o s t f[y]=f[x]+cost f[y]=f[x]+cost,并且这里我们要求的也是最小值,和最短路目的相同,所以可以用最短路算法来求出 f f f 数组。
具体来说,就是将 i i i 向 ( i + y ) m o d x (i+y)\bmod x (i+y)modx 连边,代价为 y y y,然后根据题意,要从 1 1 1 开始跑最短路。
求出 f [ i ] f[i] f[i] 后,在这个数的基础上加若干个 x x x,就可以得到其他的数,具体来说,能得到 ⌊ h − f [ i ] x ⌋ \lfloor \dfrac {h-f[i]} x \rfloor ⌊xh−f[i]⌋ 个数,加上 f [ i ] f[i] f[i] 自己,就一共有 ⌊ h − f [ i ] x ⌋ + 1 \lfloor \dfrac {h-f[i]} x \rfloor+1 ⌊xh−f[i]⌋+1 个数。
显然,对于不同的 f [ i ] f[i] f[i],肯定不会统计到相同的数,所以不需要考虑重复统计的问题,以及由于每个数模 x x x 之后肯定对应一个 i i i,进而可以被表示成 f [ i ] + k × x f[i]+k\times x f[i]+k×x 的形式,所以每个数都会被统计到。
代码如下:
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
#define maxn 100010
#define ll long long
ll h;int a,b,c;
struct edge{int y,z,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z){e[++len]=(edge){y,z,first[x]};first[x]=len;}
struct node{
int x;ll d;node(int xx=0,ll dd=0):x(xx),d(dd){}
bool operator <(const node &b)const{return d>b.d;}
};
priority_queue<node>q;
ll f[maxn];
void dij()
{
memset(f,63,sizeof(f));
q.push(node(1,1));f[1]=1;
while(!q.empty())
{
node X=q.top();q.pop();int x=X.x;if(X.d!=f[x])continue;
for(int i=first[x];i;i=e[i].next)
if(f[e[i].y]>f[x]+e[i].z)f[e[i].y]=f[x]+e[i].z,q.push(node(e[i].y,f[e[i].y]));
}
}
int main()
{
scanf("%lld %d %d %d",&h,&a,&b,&c);
if(a==1||b==1||c==1)return printf("%lld",h),0;
for(int i=0;i<a;i++)buildroad(i,(i+b)%a,b),buildroad(i,(i+c)%a,c);
dij();ll ans=0;
for(int i=0;i<a;i++)if(f[i]<=h)ans+=(h-f[i])/a+1;
printf("%lld",ans);
}