2022-04-02每日刷题打卡

本文介绍了如何使用广度优先搜索(BFS)解决将一个数字转换成另一个数字所需的最少操作数问题,并通过剪枝优化避免超时。同时,探讨了并行排序中寻找最少颜色需求的策略,利用二分+动态规划求解最长下降子序列,以减少计算复杂度。这两个问题均涉及到经典算法在实际问题中的应用和优化。
摘要由CSDN通过智能技术生成

2022-04-02每日刷题打卡

代码源——每日一题

BFS练习1 - 题目 - Daimayuan Online Judge

给你一个数字a,每次可以选择下面四种操作中的一种:

  1. 把数字a加上一。
  2. 把数字a乘以2。
  3. 把数字a乘以3。
  4. 把数字a减去一。

问把这个a变成b最少需要多少步。

你要回答q个询问,b1,b2,…,bq,输出把a变成b1,b2,…,bq的最小步数。

输入格式

第一行两个整数a,q。

接下来一行q个整数b1,…,bq。

输入格式

输出q个数字,分别表示把a变成b1,b2…,bq的最小步数。

样例输入
3 10
1 2 3 4 5 6 7 8 9 10
样例输出
2 1 0 1 2 1 2 2 1 2
数据规模

对于所有数据,保证1≤a,q,bi≤10^5。

这题也算是个很经典的bfs题了,这题的数字转化成一个树的样子就是这样:

                  a
              / |   | \
             /  |   |  \
           a*2 a*3  a-1  a+1

一个数可以向下衍生出4个不同的样子,它的四个分支又可以各自伸出4个分支。当在这个树上找到我们要转化的目标值时,第一次出现的层数就是它经过操作的次数。然后只要找完这n个点所在层数最后一起输出就行。
但是,如果我们直接这么写,是会超时的!我们每过一层,下一层要遍历节点数就是当前层的4倍,这是相当吓人的。所以我们要进行剪枝操作。
首先我们可以知道,子节点出现的值可能是会重复的,比如当前节点的值是2,当它减一后*2,又会变成2,即在下面二层的位置上会出现一个一样的数,而通过2得到的情况我们已经知道了,没有必要在这里继续延伸,继续衍生会导致很多重复的计算,这是很不划算的,所以我们要把这个子节点给剪掉。我们预先准备一个数组,记录没出现过的数,如果当前节点的数没有在数组里出现过,那我们就把这个节点存入下一层里,同时把状态修改为出现过。如果这个数之前已经出现过了,那我们就跳过他即可。
还有第二个剪枝操作,注意题目给的数据范围是1到1e5,但是我们的操作是会使得当前节点的值超过这个范围(小于1或者大于1e5),首先我们知道,既然题目给的数据没有小于1的,那我们延伸小于1的节点的四种情况就很没必要了,所以当节点小于1时,我们也直接跳过。但对于大于1e5的情况有点特殊,我们不能直接把大于1e5的情况跳过,这会使得我们的结果有错误,比如目标值是1e5,我们当前节点是50001,只要翻倍后减2就可以得到目标值了,但如果我们翻倍好的结果(1e5+2)删掉,想通过50001得到1e5的方法就只有重复+1操作49999次了。所以我们应该要适当调整我们的范围。一般来说只要是题目数据区间的稍大点即可,大过这个范围的节点我们也跳过。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 300050;
int a[N], f[N];

inline int read() {
    int x = 0; char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x;
}

void write(int x) {
    if (x > 9) write(x / 10);
    putchar(x % 10 | '0');
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n, num;
    num = read(), n = read();
    vector<int>v(n);
    for (int i = 0; i < n; i++)
    {
        v[i] = read();
        a[v[i]] = -1;
    }
    queue<int>que;
    que.push(num);
    int res = 0;
    while (!que.empty()&&n)
    {
        int len = que.size();
        for (int i = 0; i < len; i++)
        {
            int ans = que.front();
            que.pop();
            if (a[ans] == -1)
            {
                a[ans] = res;
                n--;
            }
            if (ans * 3 < 100050 && f[ans * 3] == 0)
            {
                que.push(ans * 3);
                f[ans * 3] = 1;
            }
            if (ans * 2 < 100050 && f[ans * 2] == 0)
            {
                que.push(ans * 2);
                f[ans * 2] = 1;
            }
            if (ans - 1 > 0 && f[ans - 1] == 0)
            {
                que.push(ans - 1);
                f[ans - 1] = 1;
            }
            if (ans + 1 < 100050 && f[ans + 1] == 0)
            {
                que.push(ans + 1);
                f[ans + 1] = 1;
            }
        }
        res++;
    }
    for (auto i : v)
    {
        write(a[i]); 
        putchar(' ');
    }
    
    return 0;
}
并行排序 - 题目 - Daimayuan Online Judge

给定一个 n ,以及长度为 n 的排列 a ,如果两个点 i,j 满足i<j 并且 a[i]>a[j] ,那么这两个点之间存在一条边,现在请你对这 n 个点染色,要求任意一条边的两点颜色不同,且使用的颜色数量最少,输出最少使用的颜色数量。

输入格式

第一行一行数字 T ,代表 T 组数据。

每组数据第一行一个数字 n。

接下来一行 n 个整数 a1,a2,…,an。

输出格式

一个数,表示最少需要的颜色数量。

样例1输入
2
4
1 3 4 2
2
1 2
样例1输出
2
1
数据规模

所有数据保证 1≤∑n≤10^6。

首先我们知道,只有满足i<j 并且 a[i]>a[j] 的两个点中间有一条边,而且这条边两边的两个点颜色不能一样。

然后我们知道,如果是正常的升序情况,即i<j且a[i]<=a[j]的两个点是没有边的,说明他们颜色可以一样。

那么这两个情况我们就可以知道了,序列里,只要是升序的序列用的颜色都可以一样,反之降序的必须不一样,比如4 3 2 1,1的颜色要和前面三个数的不一样,2要和前面两个不一样,3要和前面一个不一样,因为4有一种颜色,所以一共需要4种颜色才能满足要求。这样题目就变成了,最长下降子序列的长度是多少,因为只用看下降序列是什么样的,下降序列多长就要用多少种不同的颜色,升序的只用算一种就可以了。只不过这里数据长度是106,用普通的求最长下降子序列的方法是n2,显然是会超时的,所以要用上二分+dp的方法。至于二分+dp怎么求最长下降子序列可以去2022-02-12每日刷题打卡_你好_Ä的博客-CSDN博客看看,这里便不多做描述。

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<map>
#include<unordered_map>
#include<stack>
#include<list>
#include<queue>
#include<iomanip>

#define endl '\n';
typedef long long ll;
typedef pair<ll, ll>PII;
const int N = 300050;
int a[N], f[N];

inline int read() {
    int x = 0; char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x;
}

void write(int x) {
    if (x > 9) write(x / 10);
    putchar(x % 10 | '0');
}

int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        vector<int>v(n), f(n+1);
        for (int i = 0; i < n; i++)
        {
            cin >> v[i];
        }
        int len = 1;
        f[1] = v[0];
        for (int i = 1; i < n; i++)
        {
            int l = 0, r = len;
            while (l < r)
            {
                int mid = (l + r+1) / 2;
                if (f[mid] > v[i])l = mid;
                else r = mid-1;
            }
            len = max(len, r+1);
            f[r + 1] = v[i];
        }
        cout << len << endl;
    }
    
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值