【九十八】【算法分析与设计】百度之星2023部分题目,BD202301 公园,BD202303 第五维度,BD202305 糖果促销,BD202328 传信游戏

BD202301 公园

今天是六一节,小度去公园玩,公园一共 NN 个景点,正巧看到朋友圈度度熊也在这个公园玩,于是他们约定好一块去景点 NN。 小度当前所在景点编号为 TT,从一个景点到附近的景点需要消耗的体力是 TETE,而度度熊所在景点编号为 FF ,移动消耗为 FEFE。 好朋友在一块,赶路都会开心很多,所以如果小度和度度熊一块移动(即在相同位置向相同方向移动),每一步他俩的总消耗将会减少 SS。 求他俩到景点 NN 时,所需要的总消耗最少是多少?

格式

输入格式:

第一行三个数值,TE,FE,STE,FE,S ,分别代表小度移动消耗值,度度熊移动消耗值,一起移动的消耗减少值。1 \le TE,FE,S \le 40000, S \le TE+FE1≤TE,FE,S≤40000,STE+FE; 第二行四个数值,T,F,N,MT,F,N,M ,分别代表小度出发点,度度熊出发点,目标节点,总路径数。1 \le T,F,N,M \le 400001≤T,F,N,M≤40000; 接下来 MM 行,每行两个整数 X,YX,Y,代表连通的两个景点。1 \le X,Y \le N1≤X,YN

输出格式:

一个整数,即总消耗最小值。如果不能到达 NN , 输出-1。

样例 1

输入:

4 4 3

1 2 8 8

1 4

2 3

3 4

4 7

2 5

5 6

6 8

7 8

复制

输出:

22

复制

1.

图信息,邻接表存储图信息,vector<vectaor<int>>g变量存储图的信息.

外层的vector数组下标表示每一个点,下标映射变量vector<int>表示从这个点出发可以通往哪些点.

2.

一共有N个景点,选择开辟N+1空间的大小,这样下标直接表示景点不需要偏移量.

3.

小度在T点,熊在F点,他们都要去N点,可以理解他们首先会在一个景点相遇,然后前往N点,极端的情况就是他们在N景点相遇.

因此需要存储三个信息,T点出发到达所有点花费的体力,F点距出发到达所有点花费的体力,N点出发到达其他所有点花费的体力.

bfs填写这三个表即可.

 
#include<bits/stdc++.h> 
using namespace std;
#define int long long // 将 int 定义为 long long 类型,便于处理大数
#define endl '\n' // 将 endl 定义为换行符

using p=pair<int,int>; // 定义一个名为 p 的类型,表示一对整数
vector<p> d={{0,1},{1,0},{0,-1},{-1,0}}; // 定义一个表示四个方向的向量数组

/*
N 个景点,去景点 N. 1~N 个点.
小度在 T 点,走一步消耗体力 TE
熊在 F 点,走一步消耗体力 FE
如果一起走,总消耗减少 S
求最少的消耗.
*/

int TE, FE, S, T, F, N, M; // 定义输入的变量
vector<vector<int>> g; // 邻接表存储图的信息

vector<int> dis_xiaodu; // 小度到达各点的体力消耗
vector<int> dis_xiong; // 熊到达各点的体力消耗
vector<int> dis_n; // 从目标点出发到达各点的体力消耗
int ret=LLONG_MAX; // 初始化最小消耗为最大值

// BFS 填写体力消耗表
void bfs(vector<int>& dis, int start, int nums) {
    dis[start] = 0; // 起点的消耗为 0
    queue<int> q; // 定义队列进行 BFS
    q.push(start); // 起点入队
    while (!q.empty()) { // 队列不为空时进行 BFS
        auto top = q.front(); // 获取队首元素
        q.pop(); // 队首元素出队
        for (auto& x : g[top]) { // 遍历与队首元素相连的所有点
            if (dis[x] == -1) { // 如果该点未被访问
                dis[x] = dis[top] + nums; // 更新到该点的消耗
                q.push(x); // 将该点入队
            }
        }
    }
}

// 解决问题的主函数
void solve() {
    bfs(dis_xiaodu, T, TE); // 计算小度到各点的消耗
    bfs(dis_xiong, F, FE); // 计算熊到各点的消耗
    bfs(dis_n, N, TE + FE - S); // 计算从目标点到各点的消耗,考虑一起走的消耗减少
    for (int i = 1; i <= N; i++) { // 遍历所有点
        if (dis_xiaodu[i] != -1 && dis_xiong[i] != -1 && dis_n[i] != -1) // 如果三个消耗值都有效
            ret = min(ret, dis_xiaodu[i] + dis_xiong[i] + dis_n[i]); // 更新最小消耗
    }
    if (ret == LLONG_MAX) // 如果最小消耗仍为初始值,表示无法到达
        cout << -1 << endl; // 输出 -1
    else
        cout << ret; // 输出最小消耗
}

// 主函数
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 加速输入输出
    
    cin >> TE >> FE >> S; // 输入小度和熊的消耗值及一起走的消耗减少值
    cin >> T >> F >> N >> M; // 输入起点、终点及景点和路径数
    g.assign(N + 1, vector<int>()); // 初始化邻接表,大小为 N+1
    dis_xiaodu.assign(N + 1, -1); // 初始化小度到各点的消耗表,大小为 N+1
    dis_xiong.assign(N + 1, -1); // 初始化熊到各点的消耗表,大小为 N+1
    dis_n.assign(N + 1, -1); // 初始化从终点到各点的消耗表,大小为 N+1
    for (int i = 0; i < M; i++) { // 输入路径信息
        int x, y; 
        cin >> x >> y; 
        g[x].push_back(y); // 路径信息存入邻接表
        g[y].push_back(x); // 无向图,两边都要存
    }
    
    solve(); // 调用解决函数

    return 0; // 返回 0,结束程序
}

BD202303 第五维度

零维是点,点动成线;

一维是线,线动成面;

二维是面,面动成体;

三维是体,体动成史;

四维是史,史动????

现在人类企图理解第五维度。

而小度现在是第五维度的一位智者。一天,小度发现人类的许多科学家在试图理解第五维度,人类是四维生物,若是他们理解了第五维度,很可能也会到来第五维度的空间,这显然是小度不愿意看到的(毕竟哪里都有人口数量的问题….)所以小度希望他们尽可能晚的理解第五维度,因此,小度用更高维度的视角把所有人类中在理解第五维的科学家都看到了,而这些科学家的智商会不一样,所以他们的理解速度 V_iVi 也会不一样;并且,他们开始理解的时间点 S_iSi 也不一样。理解速度 V_iVi 描述为每过单位时间可获得 V_iVi 个单位理解力,也就是说在 S_i+1Si+1 的时间点该科学家会第一次贡献 V_iVi 的理解力。我们定义理解力总数超过 mm 时理解了第五维度。 小度因为维度更高,可以使用时间悖论来给人类一次重大的打击,小度可以让任意一位科学家在任意一个时间点消失,所以他接下来的理解不会继续;而且人类不会记得他,所以他之前的贡献会消失。因为小度能力有限,所以小度只能使用一次暂时悖论。

现在求在尽可能晚的情况下,人类理解第五维度的最早时间点。

时间点初始为00,但显然,没有科学家能够在 00 时刻有贡献。

格式

输入格式:

第一行给出一个整数 nn 和一个整数 mm ,表示有 nn 个科学家,在理解力总数超过 mm 时理解了第五维度; 第二至 n+1n+1 行:每行两个整数 S_iSi 和 V_iVi; 对于 100% 的数据:1≤n≤10^51≤n≤105,m≤2*10^9m≤2∗109; 对于 100% 的数据:0≤S_i≤2*10^90≤Si≤2∗109,0≤V_i≤10^30≤Vi≤103。

输出格式:

一行,包含一个整数 TT 表示在尽可能晚的情况下,人类理解第五维度的最早时间点。 若是人类永远无法理解第五维度,则输出-1。

样例 1

输入:

3 10

0 1

4 6

5 1

复制

输出:

8

复制

样例 2

输入:

3 10

0 0

4 0

5 1

复制

输出:

-1

复制

备注

对于第一个样例,使得 S_i=4Si=4 , V_i=6Vi=6 该科学家消失,则每个时刻总共理解力为:00 11 22 33 44 55 77 99 1111 ,在时刻 88 超过 m=10m=10 ,因此输出8。 对于第二个样例,人类永远无法理解第五维度,因此输出 -1−1 。

1.

二分答案法,答案的范围是0~2e9.

尽可能让答案小.

2.

二分答案,判断这个答案是否可以成为答案,成为答案的标准就是此时人类理解了第五维度,能够成为答案,记录答案,然后r=mid-1去左边找,看有没有更小的答案.如果不能成为答案,就去右边找,看右边有没有答案.

3.

怎么判断某个时间是否理解了第五维度,只需要计算这个时间所有人提供的理解力之后减去提供的最大的理解力,看此时理解力是否超过m.

(ans-start)*v就是此时这个人提供的理解力.如果ans<=start,就没法提供理解力,Continue即可.

 
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'

// 定义全局变量n和m,表示科学家数量和理解力总数阈值
int n,m;

// 定义一个结构体node,用于存储科学家的开始时间s和理解速度v
struct node{
    int s,v;
};

// 定义一个向量people,用于存储所有科学家的信息
vector<node>people;

// 定义一个变量ret,初始值为LLONG_MAX,用于存储最晚的理解时间
int ret=LLONG_MAX;

// 检查在给定的时间ans时,人类是否已经理解了第五维度
bool check(int ans){
    int maxn=-1,sum=0;
    // 遍历每个科学家
    for(auto&x:people){
        // 如果当前时间ans小于等于科学家的开始时间s,则跳过
        if(ans-x.s<=0)continue;
        // 计算当前科学家的贡献,并累加到sum
        sum+=(ans-x.s)*x.v;
        // 更新当前最大贡献值
        maxn=max(maxn,(ans-x.s)*x.v);
    }
    // 判断在去掉最大贡献值后,理解力总数是否超过阈值m
    return sum-maxn>m;
}

// 初始化函数,当前为空
void init(){}

// 求解函数,使用二分法找到尽可能晚的理解时间
void solve(){
    int l=0,r=2e9;
    // 二分查找
    while(l<=r){
        int mid=(l+r)>>1;  // 计算中间值
        // 检查中间值mid是否满足条件
        if(check(mid)){
            ret=mid;      // 更新最晚理解时间
            r=mid-1;      // 向左半部分查找更晚的时间
        }else{
            l=mid+1;      // 向右半部分查找
        }
    }
    // 如果ret未更新,输出-1;否则输出最晚理解时间
    if(ret==LLONG_MAX)cout<<-1<<endl;
    else cout<<ret<<endl;
}

// 主函数
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    
    // 读取科学家数量和理解力阈值
    cin>>n>>m;
    // 分配空间并读取每个科学家的开始时间和理解速度
    people.assign(n,node());
    for(int i=0;i<n;i++){
        cin>>people[i].s>>people[i].v;
    }
    init();  // 调用初始化函数
    solve(); // 调用求解函数
}

BD202305 糖果促销

小度最喜欢吃糖啦!!!

这天商店糖果促销,可给小度高兴坏了。

促销规则:一颗糖果有一张糖纸,pp 张糖纸可以换取一颗糖果。换出来糖果的包装纸当然也能再换糖果。

小度想吃 kk 颗糖果,他需要买多少颗糖?

格式

输入格式:

第一行一个整数 T (1 \le T \le 10^6)T(1≤T≤106) ,表示测试数据组数; 接下来TT行,每行两个整数 p_i, k_i(1 \le p_i \le 10^9, 0 \le k_i \le 10^9)pi,ki(1≤pi≤109,0≤ki≤109) ,表示第 ii 次测试中, p_ipi 张糖纸换一颗糖,小度想吃 k_iki 颗糖。

输出格式:

TT 行,每行一个整数表示需要买多少颗糖果。

样例 1

输入:

3

3 4

4 5

2 7

复制

输出:

3

4

4

复制

二分答案法

1.

二分答案法,答案的范围是0~k,对于每一个答案进行判断是否可以成为答案,成为答案的标准是对于买了ans个糖果,对于吃的糖果的数量是多少,吃了糖果的数量如果大于等于k,说明此时ans是答案,去左边看看有没有更小的答案,如果不是答案,去右边找答案.

2.

对于买了ans个糖果,计算吃了多少个糖果,也就是ans个糖纸可以交换多少个糖果,然后加上ans个糖果即可.

ans/p是第一次交换的糖果数量,交换之后剩下的糖纸是zhi%p,交换后的糖果提供糖纸.

所以此时糖纸是zhi/p+zhi%p.

如果zhi糖纸大于等于p,继续交换.

最后ans+count就是此时吃了多少个糖果.

3.

边界情况,p==1的时候,k如果是0,输出0,k如果是其他的,输出1.

如果不考虑边界情况,可能发生死循环,最好的就是简单的边界自己if考虑完毕.

 
#include<bits/stdc++.h> 

using namespace std;
#define int long long
#define endl '\n'

// 定义全局变量T,表示测试数据组数
int T;
// 定义变量p和k,分别表示换取糖果所需的糖纸数和小度想吃的糖果数
int p,k;

// 定义变量ret,初始值为LLONG_MAX,用于存储需要购买的最少糖果数
int ret=LLONG_MAX;

// 检查在购买ans颗糖果时,能否通过交换糖纸满足小度想吃k颗糖果的需求
bool check(int ans){
    int count=0;  // 记录通过交换获得的糖果数量
    int zhi=ans;  // 初始糖纸数量等于购买的糖果数量
    while(!(zhi<p)){  // 当糖纸数量大于等于p时,继续交换
        count+=zhi/p;  // 通过交换获得的糖果数量
        zhi=zhi/p+zhi%p;  // 更新剩余糖纸数量
    }
    // 判断购买的糖果数量加上交换获得的糖果数量是否大于等于k
    return ans+count>=k;
}

// 求解函数,使用二分法找到最少需要购买的糖果数
void solve(){
    ret=LLONG_MAX;  // 重置ret值

    // 边界情况处理,如果p为1
    if(p==1){
        if(k==0)cout<<0<<endl;  // 如果k为0,输出0
        else cout<<1<<endl;  // 否则输出1
        return;
    }

    int l=0,r=k;  // 二分查找的范围为0到k
    while(l<=r){
        int mid=(l+r)>>1;  // 计算中间值
        if(check(mid)){  // 检查mid是否满足条件
            ret=min(ret,mid);  // 更新最少购买的糖果数
            r=mid-1;  // 向左半部分查找更小的答案
        }else{
            l=mid+1;  // 向右半部分查找
        }
    }
    cout<<ret<<endl;  // 输出结果
}

// 主函数
signed main( )
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>T;  // 读取测试数据组数
    while(T--){
        cin>>p>>k;  // 读取每组测试数据中的p和k

        solve();  // 调用求解函数
    }

    return 0;
}

数学方法

1.

我们需要吃k个糖果,提供了k个糖纸,最后的糖纸数一定是剩下1个,说明k-1的糖纸全部交换成了糖,k-1/p就是交换了的糖数.

买的糖+交换的糖=k.

所以买的糖=k-交换的糖.

 
#include <bits/stdc++.h> 

using namespace std;
#define int long long
#define endl '\n'

// 定义全局变量T,表示测试数据组数
int T;
// 定义变量p和k,分别表示换取糖果所需的糖纸数和小度想吃的糖果数
int p, k;
// 定义变量ret,用于存储需要购买的糖果数
int ret;

void solve() {
    ret = 0; // 初始化ret为0

    // 如果k为0,表示不需要糖果,输出0
    if(k == 0) 
        cout << 0 << endl;
    else {
        // 根据公式计算需要购买的糖果数
        // ret = k - (k-1)/p,其中(k-1)/p表示通过交换获得的糖果数
        ret = k - (k-1)/p;
        // 输出结果
        cout << ret << endl;
    }
}

// 主函数
signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 加速输入输出
    cin >> T; // 读取测试数据组数
    while (T--) {
        cin >> p >> k; // 读取每组测试数据中的p和k
        solve(); // 调用求解函数
    }

    return 0;
}

  BD202328 传信游戏

N 个人在一起玩传信游戏,编号为 1,…,N1,…,N, 每个人拿到信之后要么会将信传递给下一个人,要么会将信自己收下来。 具体而言,编号为 ii 的人拿到信后会将信传递给编号为 t_iti 的人,如果 t_i=0ti=0 则表示自己收下。 现在发现有的信送出后,会在一些人的手中循环传递。 小度想知道从哪些人手中开始传信,最终不会出现循环传递的现象,输出这些人编号的异或值。

格式

输入格式:

第一行,一个整数 NN, 1 \le N \le 10001≤N≤1000; 接下来 NN 行,每行一个整数,共 NN 个数,第 ii 个数 t_iti 表示编号为 ii 的人传信对象为 t_iti,t_iti 为 00 表示他将信自己收下,不会出现 t_i = iti=i 的情况, 0 \le t_i \le N 0≤tiN

输出格式:

一行,一个整数,不会出现循环传递的人编号异或值。

样例 1

输入:

5

3

3

1

0

4

复制

输出:

1

复制

备注

样例中1,2,3的信件最终会循环传递,4,5号的信件不会,异或值为1。

1.

快慢指针判断是否有环,快指针走两步,慢指针走一步,如果有环相遇点一定不是0,如果没有环相遇点一定是0.

 
#include<bits/stdc++.h> 

using namespace std;
#define int long long 
#define endl '\n'

int N; // 定义一个整数N,用于存储总人数
vector<int>arr; // 定义一个向量arr,用于存储每个人传信对象

int ret; // 定义一个整数ret,用于存储最终的异或结果

// 函数check用于检查从某个人开始传信,最终是否会进入循环
bool check(int i) {
    int fast = i, slow = i; // 定义快慢指针,初始化都为i
    do {
        fast = arr[arr[fast]]; // 快指针每次走两步
        slow = arr[slow]; // 慢指针每次走一步
    } while (!(fast == slow)); // 如果快慢指针相遇则退出循环
    if (fast != 0)return false; // 如果快慢指针相遇且不是0,则存在环
    else return true; // 如果快慢指针相遇且是0,则不存在环
}

// 函数solve用于计算最终不会出现循环传递的人编号异或值
void solve() {
    ret = 0; // 初始化异或结果为0

    for (int i = 1; i <= N; i++) { // 遍历每个人
        if (check(i)) { // 如果从第i个人开始传信不会出现循环传递
            ret ^= i; // 将该人的编号与当前结果进行异或操作
        }
    }
    cout << ret << endl; // 输出最终结果
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 加速输入输出
    cin >> N; // 输入总人数N
    arr.assign(N + 1, 0); // 初始化向量arr的大小为N+1,初始值为0
    for (int i = 1; i <= N; i++) cin >> arr[i]; // 输入每个人的传信对象
    solve(); // 调用solve函数计算并输出结果
    return 0; // 返回0,表示程序正常结束
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

妖精七七_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值