最短路应用 —— 解决某些计数、数论问题

29 篇文章 0 订阅
10 篇文章 0 订阅
1. Elevator (Gym-241680 E)

题意: 一个高度为 h h h 的电梯,初始位置在第一层。电梯有四个按钮。

  1. 向上移动 a a a
  2. 向上移动 b b b
  3. 向上移动 c c c
  4. 返回第一层

1 ~ h 1~h 1h 层中有多少层是可达的。 ( 1 ≤ h ≤ 1 0 18 , 1 ≤ a , b , c ≤ 100000 ) (1\leq h\leq 10^{18},1\leq a,b,c\leq 100000) (1h1018,1a,b,c100000)

思路: 观察数据范围可以发现, a 、 b 、 c a、b、c abc 数据范围比较小,可以从此处着手。

不难发现,如果 2 2 2 可以达到,则 2 + a 2+a 2+a 2 + 2 ∗ a 2+2*a 2+2a 2 + 3 ∗ a 2+3*a 2+3a… 均可到达。即我们以 a a a 作为基底,如果 x   ( x &lt; a ) x\ (x&lt;a) x (x<a) 可以到达,则 y   ( y % a = x ) y\ (y\%a=x) y (y%a=x) 也可到达。

因此问题转化为对于 x ( x &lt; a ) x(x&lt;a) x(x<a) 来说,最小的可达的 y y y 是多少? ( y % a = x ) (y\%a=x) (y%a=x) 既然问题变成了求最小,那么不难想到使用最短路来解决这个问题。

我们枚举 x   ( 0 ≤ x &lt; a ) x\ (0\leq x&lt;a) x (0x<a),令 x x x ( x + b ) % a (x+b)\%a (x+b)%a ( x + c ) % a (x+c)\%a (x+c)%a 连边,边权分别为 b b b c c c。然后起点为 1 1 1,即可求出到达所有 x x x 时的最小值。

求取答案时就可以枚举 x x x,然后求 d i s [ x ] ~ h dis[x]~h dis[x]h 中有多少个 m o d   a = x mod\ a=x mod a=x 的值即可。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const int M = 1e6+100;
const ll inf = 1e15;
const db EPS = 1e-9;
using namespace std;
 
ll h,a,b,c,dis[N];
int head[N],tot,vis[N];
struct Node{
	int to,next;
	ll w;
}e[M];
 
void init() {tot = 1;}
 
void add(int x, int y, ll w){
	e[++tot].to = y, e[tot].next = head[x], head[x] = tot, e[tot].w = w;
}
 
void dijkstra(int s){
	priority_queue<pair<ll,int> > q;
	while(q.size()) q.pop();
	rep(i,0,a-1) dis[i] = inf, vis[i] = 0;
	dis[s%a] = 1;
	q.push(make_pair(-dis[s%a],s%a)); 
	while(q.size()){
		int x = q.top().second; q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		for(int i = head[x]; i; i = e[i].next){
			int y = e[i].to;
			if(dis[y] > dis[x] + e[i].w){
				dis[y] = dis[x] + e[i].w;
				q.push(make_pair(-dis[y],y));
			}
		}
	}
}
 
int main()
{
	freopen("elevator.in","r",stdin);
	freopen("elevator.out","w",stdout);
	scanf("%lld%lld%lld%lld",&h,&a,&b,&c);
	init();
	rep(i,0,a-1){
		add(i,(i+b)%a,b);
		add(i,(i+c)%a,c);
	}
	dijkstra(1);
	ll ans = 0;
	rep(i,0,a-1){
		if(dis[i] == inf || dis[i] > h) continue;
		ans += 1ll + (h-dis[i])/a;
	}
	printf("%lld\n",ans);
	return 0;
}
2. Sums (Gym-100753 M)

题意: n n n 个数, k k k 组询问。每组询问给出一个 b b b,问能否用这 n n n 个数组成 b b b ( 1 ≤ a i ≤ 5000 , 1 ≤ b i ≤ 1 0 9 ) (1\leq a_i\leq 5000,1\leq b_i\leq 10^9) (1ai5000,1bi109)

思路: n 2 n^2 n2 建图,直接跑最短路得到答案即可。

代码:

//以a为循环,求出 0~a-1 中每一个数字第一次被到达的数字
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
typedef long long ll;
typedef double db;
const int N = 1e5+100;
const ll inf = 1e15;
const db EPS = 1e-9;
using namespace std;
 
ll dis[N];
int n,a[N],vis[N],k;
 
void dijkstra(int s){
	priority_queue<pair<ll,int> > q;
	while(q.size()) q.pop();
	rep(i,0,a[1]-1) dis[i] = inf, vis[i] = 0;
	dis[0] = 0;
	q.push(make_pair(0,0)); 
	while(q.size()){
		int x = q.top().second; q.pop();
		if(vis[x]) continue;
		vis[x] = 1;
		rep(i,2,n){
			int y = (x+a[i])%a[1];
			if(dis[y] > dis[x] + a[i]){
				dis[y] = dis[x] + a[i];
				q.push(make_pair(-dis[y],y));
			}
		}
	}
}
 
int main()
{
	scanf("%d",&n);
	rep(i,1,n) scanf("%d",&a[i]);
	dijkstra(0);
	scanf("%d",&k);
	rep(i,1,k){
		int tp,hp; scanf("%d",&tp);
		hp = tp%a[1];
		if(tp >= dis[hp]) printf("TAK\n");
		else printf("NIE\n");
	}
	return 0;
}
3. 墨墨的等式 (Bzoj-2118)

题意: 对于 a 1 ∗ x 1 + a 2 ∗ x 2 + . . . + a n ∗ x n = B a_1*x_1+a_2*x_2+...+a_n*x_n=B a1x1+a2x2+...+anxn=B 是否存在非负整数解。给出 n 、 a i n、a_i nai 以及 B B B 的范围,求出有多少个 B B B 使等式存在非负整数解。 ( 1 ≤ N ≤ 12 , 0 ≤ a i ≤ 5 ∗ 1 0 5 , 1 ≤ B M i n ≤ B M a x ≤ 1 0 12 ) (1\leq N\leq 12,0\leq a_i\leq 5*10^5,1\leq BMin\leq BMax\leq 10^{12}) (1N12,0ai5105,1BMinBMax1012)

思路: 与上面题目做法一致,选取最小的 a a a 作为基底,然后 n 2 n^2 n2 建图,求出第一次到达 0 ~ a − 1 0~a-1 0a1 中各数字的最小值,然后统计答案即可。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define LOG1(x1,x2) cout << x1 << ": " << x2 << endl;
#define LOG2(x1,x2,y1,y2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << endl;
#define LOG3(x1,x2,y1,y2,z1,z2) cout << x1 << ": " << x2 << " , " << y1 << ": " << y2 << " , " << z1 << ": " << z2 << endl;
typedef long long ll;
typedef double db;
const int N = 20+100;
const int M = 12*5*1e5+100;
const db EPS = 1e-9;
using namespace std;
 
int n,head[M],tot,vis[M];
ll l,r,a[N],ans,dis[M];
struct Edge{
    int to,next,w;
}e[M];
 
void add(int x,int y,int z){
    e[++tot].to = y, e[tot].next = head[x], head[x] = tot, e[tot].w = z;
}
 
priority_queue< pair<ll,int> > q;
 
void dijkstra(int s){
    while(q.size()) q.pop();
    memset(dis,0x3f,sizeof dis);
    dis[s] = 0; //第一次到达%a[1] == 0的点为0
    q.push(make_pair(0,s));
    while(q.size())
    {
        int x = q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x] = 1;
        for(int i = head[x]; i; i = e[i].next){
            int y = e[i].to, z = e[i].w;
            if(dis[y] > dis[x]+z){
                dis[y] = dis[x]+z;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
 
}
 
int main()
{
    scanf("%d%lld%lld",&n,&l,&r);
    int ctt = 0;
    rep(i,1,n){
        ll tpp; scanf("%lld",&tpp);
        if(tpp != 0) a[++ctt] = tpp;
    } 
    n = ctt;
    if(n == 0) printf("0\n");
    else{
        rep(i,1,n)
            if(a[1] > a[i]) swap(a[1],a[i]); //找到最小的,降低复杂度
        rep(i,0,a[1]-1){
            rep(j,2,n){
                add(i,(i+a[j])%a[1],a[j]);
            }
        }
        dijkstra(0);
        rep(i,0,a[1]-1){
            ll tp1 = 0, tp2 = 0;
            if(l-1-dis[i] >= 0)
                tp1 = (l-1-dis[i])/a[1]+1;
            if(r-dis[i] >= 0)
                tp2 = (r-dis[i])/a[1]+1;
            ans += tp2-tp1;
        }
        printf("%lld\n",ans);
 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值