解决问题:
1.给定n个整数,求这n个整数能拼出多少个其他整数(可以重复取)。
2.给定n个整数,求这n个整数不能拼出的最大/最小整数。
3.至少要拼几次才能拼出模k余p的数。
类比差分约数,都是通过等式类比最短路,同余最短路状态转移通常是f(i+y)=f(i)+y。
类似单源最短路:f(v)=f(u)+w(u,v)。建边就是add(i,(i+j)%x,j)。表示可以从i到i+j,但是i+j大于等于x的时候要取模,距离是j。
对于x,一般取n个数里最小的数,剩余系最小。
跳楼机:
求1+x+y+z小于等于h的方案数,我们令h--,那么就变成x+y+z小于等于h的方案数。
如果我们将所有方案都%x,那么答案就可以划分成x组,【0,x-1】,只需要求每个余数的答案个数之和既可。
思考3x+b和5x+b可以构成的答案。这里b是小于x的。发现3x+b再+2x就等于5x+b。但是如果我们都%x后都是等于b,如果对于3x+b和5x+b我们都算一次答案(也就是加上多少个x仍然小于等于h),答案会重复算(甚至后面的答案都会重复)。仔细观察,可以发现对于余数为b的kx+b答案,我们只需要找出这些答案里的最小值,它的答案就包括所有余数为b的答案。
比如:h=14,x=4,y=7,z=9,(h已经减过1了),求%x==0的答案,也就是0,4,8,12.
如果我们找到最小值0,那么计算答案(h-dis【0】)/x+1就是4。h-dis【0】就是算h离最小值中间有多少距离,除以x,就是算出这些距离里有多少个答案,+1就是最小值本身。
那么问题就转化成求每个余数的最小值,直接跑最短路既可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
#define int ll
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
typedef unsigned long long ull;
//const ll P=2281701377;
const ll mod=998244353;
typedef pair<int,int> pii;
int n,m;
int a[N],b[N];
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
ull dis[N];
int h,x,y,z;
void dij(){
for(int i=0;i<=x;i++){
dis[i]=-1;
}
vector<int> vis(x+1);
dis[0]=0;
priority_queue<pii,vector<pii>,greater<pii> > q;
q.push({0,0});
while(q.size()){
auto t=q.top();
q.pop();
int bh=t.second;
vis[bh]=1;
int t1=(bh+y)%x;
if(!vis[t1]&&dis[t1]>dis[bh]+y){
dis[t1]=dis[bh]+y;
q.push({dis[t1],t1});
}
int t2=(bh+z)%x;
if(!vis[t2]&&dis[t2]>dis[bh]+z){
dis[t2]=dis[bh]+z;
q.push({dis[t2],t2});
}
}
}
void solve(){
cin>>h>>x>>y>>z;
h--;
dij();
int ans=0;
for(int i=0;i<x;i++){
if(dis[i]>h) continue;
ans+=(h-dis[i])/x+1;
}
cout<<ans;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
}
Small Multiple:
将所有数转化成k的同余系,我们用dis【i】表示余数为i的那些数的各个位数之和的最小值,那么dis【0】也就是余数为0,是k的倍数的最小dis就是答案。
观察到任意一个正整数都可以从1开始,按照某种顺序执行乘10加1的操作,最终得到,而其中加1操作的次数就是这个数的数位和。这提示我们使用最短路。会发现如果一个数连续走10次+1操作,或者说+1操作产生进位,不符合上述权值变化,但是发现这样的操作会先由合法的操作得到,且dis一定是小于不合法操作得到的,所以不会被不合法操作更新。比如15可以被1*10+5得到,这时候15的最小dis就是6(因为dis【1】是1),也可以被5加10次数的1得到,这样的dis是15,肯定比6大,不会更新,所以不合法的肯定不会是正确答案。
可以用双端队列,也可以用普通的bfs。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
#define int ll
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
typedef unsigned long long ull;
//const ll P=2281701377;
const ll mod=998244353;
typedef pair<int,int> pii;
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int dis[N];
int k;
vector<int> v[N],l[N];
void dij(){
memset(dis,0x3f,sizeof dis);
vector<bool> vis(N,0);
dis[1]=1;
deque<int> q;
q.push_front(1);
while(q.size()){
auto t=q.front();
q.pop_front();
if(vis[t]) continue;
vis[t]=1;
for(int i=0;i<v[t].size();i++){
int j=v[t][i],w=l[t][i];
if(dis[j]>dis[t]+w){
dis[j]=dis[t]+w;
if(w==0)
q.push_front(j);
else
q.push_back(j);
}
}
}
}
void solve(){
cin>>k;
for(int i=1;i<=k;i++){
int t=(i + 1 > k ? 1 : i + 1);
v[i].push_back(t);
l[i].push_back(1);
t=(i*10)%k;
v[i].push_back(t);
l[i].push_back(0);
}
dij();
cout<<dis[0];
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
}
普通bfs不要上面的vis标记
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
#define int ll
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
typedef unsigned long long ull;
//const ll P=2281701377;
const ll mod=998244353;
typedef pair<int,int> pii;
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
int dis[N];
int k;
vector<int> v[N],l[N];
void dij(){
memset(dis,0x3f,sizeof dis);
dis[1]=1;
deque<int> q;
q.push_front(1);
while(q.size()){
auto t=q.front();
q.pop_front();
for(int i=0;i<v[t].size();i++){
int j=v[t][i],w=l[t][i];
if(dis[j]>dis[t]+w){
dis[j]=dis[t]+w;
q.push_back(j);
}
}
}
}
void solve(){
cin>>k;
for(int i=1;i<=k;i++){
int t=(i + 1 > k ? 1 : i + 1);
v[i].push_back(t);
l[i].push_back(1);
t=(i*10)%k;
v[i].push_back(t);
l[i].push_back(0);
}
dij();
cout<<dis[0];
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
while(t--){
solve();
}
}