问题背景
给出 n 个整数,每个整数可以取任意多次,询问关于它们能拼凑出的数的一些信息
即用于求解形如“给出 n 个数 a1,a2,...,an ,求有多少个正整数 b 满足 b≤T 能使得 ∑i=1i=naixi=b 存在非负整数解”一类的问题。
首先先看一道例题 跳楼机
大致题意,你现在有一个数 h ,初始值为 1 。现在你可以进行以下四种操作若干次:
- 使 h=h+a
- 使 h=h+b
- 使 h=h+c
- 使 h=1
实际上就是问你对于给定的 a,b,c ,存在多少的数字 k 使得 ,其中
(为了方便起见,下述均将起点视为0,即整体往下移动了一位)
对于上述等式,我们考虑到,如果 k 是存在的,即 ,那么对于我们可以想到,下面等式
( p 是一个非负整数)也是成立的,然后我们发现 k≡(k+ap)moda ,也就是说只要我们能凑出 k ,那么只需满足在范围内,
都是可以凑出来的,因为x是任意的,对于同余我们有如下,记 q=kmoda ,那么只需要对于所有满足
( ,p,k 模 a 的余数相同),并且
(题目范围)的正整数 p 都是合法的,其中 p 的个数就应该是 [(H−k)/a]+1 ,至于为什么是这个我们举个例子,假设 p 可取的值为
H 是上界,表示取不到),那么我们可取的数字就是三个,便是上式,加1是因为端点值 k 也是需要算上的。
了解了这里之后我们便有如下思考,既然找到一个 k ,那他后面可以凑出来的数字我们就知道了,不需要再去计算出来具体的了,于是我们想到要是 k 最小就好了,这样我们可以省去很多计算,直接带入上述公式就可以了。
那么更一般的,设 表示满足 k≡i(moda) 的最小
,于是可以凑出 i 的个数便是 [(H−di)/a]+1 ,那么如果我们能计算出所有的
即
那我们的答案就是
那么我们现在的问题就是如何来求解
以上述题目为例,假设我们现在考虑到的某个数为 k , p=kmoda
- 若使用了一次 k=k+a ,那么 (k+a)moda=kmoda 余数不变,仍为 p 。
- 若使用了一次 k=k+b ,那么余数 p=(k+b)moda=(p+b)moda 。
- 若使用了一次 k=k+c ,那么余数 p=(k+c)moda=(p+c)moda 。
考虑到状态转移
- 若使用了一次 k=k+a,那么本次的 dp+a=dp 没有任何变化,因为p+a取模还是p的倍数。
- 若使用了一次 k=k+b ,那么有 dp⟶(b)dp+bmoda 。
- 若使用了一次 k=k+c ,那么有 dp⟶(c)dp+cmoda。
于是我们便找到了所有的 di ,接下来就是如何求解,考虑到与之类似的形式并且又要求出最小的 di ,于是我们最短路中的 disv=disu+wi ,其中 wi 表示从u到v的一段距离,对比上述式子,我们可以把 [0,a−1] 范围内的数都抽象为点( moda 的余数),每一次转移都抽象为边,从而我们得到下述求解过程:
- d0=0 ,即视作 0 为源点。
- 初始化 d1,d2,da−1 为无穷大。
- 连接一条形如 x⟶(b)(x+b)moda 的边。
- 连接一条形如 x⟶(c)(x+c)moda 的边。
接下来跑一遍最短路算法,便可以得到小的 di 即满足 di≡i(moda) 并且最小的数
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<int,int>PII;
const int N=1e5+10;
int h[N],e[N*2],ne[N*2],w[N*2],idx;
int dis[N],st[N];
int H,x,y,z;
void add(int a,int b,int c)
{
e[++idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx;
}
void dij()//dijkstra
{
memset(dis,0x3f,sizeof dis);
priority_queue<PII,vector<PII>,greater<PII>>q;
dis[1]=1;q.push({1,1});
while(q.size())
{
int u=q.top().second,d=q.top().first;q.pop();
if(st[u])continue;
st[u]=1;
for(int i=h[u];i;i=ne[i])
{
int v=e[i];
if(dis[v]>d+w[i])
{
dis[v]=d+w[i];
q.push({dis[v],v});
}
}
}
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
cin>>H>>x>>y>>z;
if(x==1||y==1||z==1)//只要有一个为1我们便可以到达任意层
{
cout<<H;return 0;
}
for(int i=0;i<x;i++)
{
add(i,(i+y)%x,y),add(i,(i+z)%x,z);//建立边
}
dij();
int ans=0;
for(int i=0;i<x;i++)
{
if(dis[i]<=H)
ans+=(H-dis[i])/x+1;//推出的等式
}
cout<<ans;
return 0;
}