P3403 跳楼机 和 ARC084B Small Multiple 同余最短路

题目链接

        解决问题:

        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();
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值