前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员进阶之算法练习(四十四)

程序员进阶之算法练习(四十四)

作者头像
落影
修改2023-09-21 19:25:03
4000
修改2023-09-21 19:25:03
举报
文章被收录于专栏:落影的专栏落影的专栏

正文

题目1

题目链接 题目大意: 给出整数x,求两个整数a和b,满足: ???(?,?)+???(?,?)=? . GCD是最大公约数; LCM是最小公约数;

题目保证a和b存在;

输入: 第一行整数t,表示样例个数; (1≤?≤100) 接下来t个样例,每个样例一行,整数x;(2≤?≤10^9)

输出: 整数a和b; (要求范围,1≤?,?≤10^9)

Examples input 2 2 14 output 1 1 6 4

题目解析: 构造题,这里提供一种方法:1+(x-1)。

构造题,想到就简单;想不到就很难。 如果不能马上想到规律,可以尝试将数字设置小一些,比如说x=10; 再多考虑x=11、x=12这些,探索规律,然后将x=100进行验证。

int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        cout << 1 << " " << n - 1 << endl;
    }
题目2 CopyCopyCopyCopyCopy

题目链接 题目大意: 给出n个整数的数组,现在将数组复制,放在数组末尾,重复n次; 求新的数组中,最长严格递增子序列的长度是多少?

输入: 第一行整数t,表示样例个数; (1≤?≤100) 接下来t个样例,每个样例2行; 第一行,整数? (1≤?≤10^5) 第二行,n个整数?1, ?2, …, ?? (1≤??≤10^9) 每个样例一行,表示最长递增子序列的长度;

Examples input 2 3 3 2 1 6 3 1 4 1 5 9 output 3 5 样例解释,3,2,1重复3次,得到[3,2,1,3,2,1,3,2,1] 最长递增子序列是[1,2,3],长度是3;

题目解析: 如果n个数字不相同,重复n次之后,必然可以选出n个数字,最长递增子序列的长度是n;(因为重复n次,每次选1个数字就好) 如果n个数字中存在相同,则相同的数字只能取一个; 所以答案就是数组中,数字不同的个数。

转换思路,题目变成求数组中不同数字的数量。

    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        
        int ans = 0;
        map<int, int> mp;
        for (int i = 0; i < n; ++i) {
            int k;
            cin >> k;
            if (!mp[k]) {
                mp[k] = 1;
                ++ans;
            }
        }
        cout << ans << endl;
    }
题目3 Numbers Exchange

题目链接 题目大意: 小明喜欢一种字符串Zebras,Zebras的定义是以0开头,以0结束,中间0/1交替出现,比如说:0, 010, 01010; 类似1, 0110, 0101这种就不是Zebras; 现在给出一串长度为n的字符串(全部是01组成); 小明希望把字符串分成若干个子序列,并且每个子序列都是Zebras;(每个字符存在子序列中,且只在一个) 如果不能,则输出-1;

输入数据: 一串字符串,长度不超过20w;

输出数据: 第一行,数字k,表示分为k个子序列;

Examples input 0010100

output 3 3 1 3 4 3 2 5 6 1 7 样例解释: 分3个子序列010,010,0;

题目解析: 由贪心知道,01010..的序列越长越好;(0的利用率提升) 题目的要求是每个字符都要放到子序列里去,那么有一种简单的做法: 每次从左到右扫描,在未选择的字符中,寻找最长的Zebras,比如说对样例0010100处理: 第一次扫描,扫出01010;(1,3,4,5,6) 第二次扫描,扫出0;(2) 第三次扫描,扫出0;(7)

但是这样的代价比较高,是N^2级别复杂度;(对于00001111000这样的数据) 考虑这样一种优化方案: 有两个队列x、y,队列x是0结束的子序列,队列y是1结束的子序列;

当遇到0时,从队列y取一个子序列,末尾拼0然后放入队列x;如果队列y没有数据,则新建一个子序列(因为0可以是单独的序列),然后将子序列放入队列x; 当遇到1时,从队列x取一个子序列,末尾拼1然后放入队列y;如果队列x没有数据,则无解;

这样每次都是O(1)的操作,总体的复杂度是O(N);

    cin >> str;
    size_t n = strlen(str);
    
    queue<int> q[2];
    int id = 0;
    int ok = 1;
    for (int i = 0; i < n && (ok>0); ++i) {
        if (str[i] == '0') {
            if (q[1].empty()) {
                ans[id].push_back(i);
                q[0].push(id++);
            }
            else {
                int x = q[1].front();
                q[1].pop();
                ans[x].push_back(i);
                q[0].push(x);
            }
        }
        else {
            if (q[0].empty()) {
                ok = -1;
            }
            else {
                int x = q[0].front();
                q[0].pop();
                ans[x].push_back(i);
                q[1].push(x);
            }
        }
    }
    if (ok > 0 && q[1].size() == 0) {
        cout << id << endl;
        for (int i = 0; i < id; ++i) {
            cout << ans[i].size() << " ";
            for (int j = 0; j < ans[i].size(); ++j) {
                cout << ans[i][j] + 1 << " ";
            }
            cout << endl;
        }
    }
    else {
        cout << -1 << endl;
    }
题目4 A Leapfrog in the Array

题目链接 题目大意: 有若干个格子排成一行序号是1、2、3、4、5、、、,有1~n的n个整数分别放在格子里,第i个数字放在第2i-1格子; 现在对数字进行操作: 每次把最右边的数字x,放在x左边最接近的空格里; 不断重复,直到x左边没有空的格子;

现在有q个询问,每个询问是数字x[i],求第x[i]个格子上面的数字;

输入数据: n and q (1 ≤ n ≤ 1e18, 1 ≤ q ≤ 200 000) 接下来q行,每行一个数字x[i];

输出数据: 对于每个询问,输出第x[i]个格子上面的数字;

Examples input 4 3 2 3 4 output 3 2 4

样例解释,见上图。

题目解析: 从题目的操作结果来看,最后操作停止的结果必然是n个数字分布在第1到n个格子里; 容易知道,小于等于n/2的数字是不会动的。(因为数字的左边没有格子) 相当于后面(x - n/2)数字排列紧密(没有空格),然后数字的最后一个跳到前面; 于是可以用递归的思想解决问题,不断重复,直到数字剩下1个。

思考?: 另一种解法: 我们用0表示空格,当n=8的时候,原序列为1,0,2,0,3,0,4,0,5,0,6,0,7,0,8 把每次移动的数字写出来: 8,8,7,8,6,7,5,8,4,6,3,7,2,5,1。 或许看不出来问题? 试试这么看: 8, 8,7 8,6,7,5, 8,4,6,3,7,2,5,1 每次都是从n-2^(i-1)开始,按顺序插入上一行的数字。 从题意知道,每次都会填补数字最左边的一个空格。 又根据移动的性质,我们知道填补第n个格子数字不会再移动。(因为当第n个格子成为最右边的数字时,左边已经没有格子) 所以只需要算出n个数中间的格子是倒数第k个空格,从上面的序列选择第k个数字,就是n个数字移动后最右边一个数字。

/**
 1 2 3 4 5 6 按照原来的规则进行选择,最右边的一个

 @param n 前n个
 @return 最右边的一个
 */
lld solve(lld n) {
    if (n % 2) {
        return (n + 1) / 2;
    }
    return n / 2 + solve(n - n / 2);
}

int main(int argc, const char * argv[]) {
    // insert code here...
    lld n, q;
    cin >> n >> q;
    while (q--) {
        lld x;
        cin >> x;

        if (x % 2 == 1) {
            cout << (x + 1) / 2 << endl;
        }
        else {
            cout << solve(n - x / 2) + x / 2 << endl;
        }
    }
    
    
    return 0;
}

总结

题目1 是简单构造题,构造题也有一些思考门路,从小样例开始分析规律; 题目2 是考察思路,代码很简单,核心在于有没有想到题目其实就是求数组的不同数字; 题目3 是考察实现能力,可能方法会有很多,但是需要找一个简单快捷方案,并且能输出答案; 题目4 看似很复杂,其实考虑下n=3、n=4、n=5,模拟下操作可以发现规律。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文
    • 题目1
      • 题目2 CopyCopyCopyCopyCopy
        • 题目3 Numbers Exchange
          • 题目4 A Leapfrog in the Array
          • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档