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

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

作者头像
落影
发布2023-05-27 14:08:16
1690
发布2023-05-27 14:08:16
举报

题目1

题目链接 题目大意: 给出n个整数的数组a,数组的元素可能是1或者2; 现在想要找到一个最小的位置k满足: 𝑎1⋅𝑎2⋅…⋅𝑎𝑘=𝑎𝑘+1⋅𝑎𝑘+2⋅…⋅𝑎𝑛

输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤100) 每个样例2行,第一行整数𝑛 (2≤𝑛≤1000) 第二行n个整数,𝑎1,𝑎2,…,𝑎𝑛 (1≤𝑎𝑖≤2).

输出: 每个样例一行,输出最小的位置k;如果不存在k,则输出-1;

Examples input 3 6 2 2 1 2 1 2 3 1 2 1 4 1 1 1 1

output 2 -1 1

题目解析: 由于题目数字取值范围只有1和2,1不影响乘积,那么只需要考虑2; 如果数组中有偶数个整数,那么题目有解; 注意如果是0个数字2,那么答案是1;

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            vector<int> pos;
            for (int i = 1; i <= n; ++i) {
                int tmp;
                cin >> tmp;
                if (tmp == 2) pos.push_back(i);
            }
            int ans = 1;
            if (pos.size() % 2) ans = -1;
            else if (pos.size() > 0) ans = pos[pos.size() / 2 - 1];
            
            cout << ans << endl;
        }
    }
}

题目2

题目链接 题目大意: 给出一个整数n,将整数n拆成两个整数A和B,要求满足: A+B=n; A和B的数字和最多相差1;

数字和的意思就是将整数A的各个位置数字相加,比如说198就是1+9+8=18,208就是2+0+8=10;

输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤10000) 每个样例1行,第一行整数𝑛 (2≤𝑛≤1e9)

输出: 每个样例一行,输出A和B,题目保证结果存在;

Examples input 5 1 161 67 1206 19 output 1 0 67 94 60 7 1138 68 14 5

题目解析: 当n是偶数的时候,A=B=n/2,必然满足要求; 当n是奇数时,我们可以令A=n/2, B=n - n/2,这样有A和B必然满足A+B=n情况,接下来讨论AB数字和相差1的的情况: 1、当A的个位数小于9时,A和B必然数字和只差1,比如说81和82,78和79; 2、当A的个位数等于9时,由于B会进位,最终A和B的数字和会产生一个差值,比如说9和10,39和40,99和100; 由此我们可以产生一个朴素的做法:令A--,B++,最终肯定能够找到符合要求的数字;

但是这么做的话,会出现超时(TLE)的情况,我们来分析下复杂度。 当数字为999和1000时,数字和相差(9+9+9) - 1 = 26的情况,那么要A的数字和-13,B的数字和+13; 我们知道获得数字和13最快的情况是十位数4+个位数9,那么结果为A=999-49=950,B=1000+49=1049; 如果采用枚举的复杂度,需要枚举49次;

最坏的情况,数字n为199999999,A=99999999,B=100000000,A和B的数字和相差(9 * 8) - 1=71,也就是说A需要减去数字和35,B需要加上数字和36; 最终数字A为 99999999 - 8999 = 99991000,数字和是72-35=37; 最终数字B为100000000 + 8999 = 1000089999,数字和是1+36=37; 枚举的复杂度是10000左右,加上每次计算数字和的复杂度 和 样例数量,最终复杂度为10000 * 10 * 10000 = 1e9,复杂度偏高;

优化方式1:每次在计算A--和B++的时候,直接按照数字和一半进行操作,比如说数字和一半是37,那么A-=37,B+=37,这样效率能加快一些;因为最终数字和会逐步收敛,结果保证是正确的;

优化方式2:直接按照数字和之差,从个位数开始往高位填9,直到数字和小于9,则在当前位数填下剩余数字,比如说37就得到8999,13就得到49;这样可以O(1)得到最优解。

class Solution {
    static const int N = 201010;
    int digSum(int x) {
        int ret = 0;
        while (x) {
            ret += x % 10;
            x /= 10;
        }
        return ret;
    }
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            int x = n / 2, y = n - x;
            while (abs(digSum(x) - digSum(y)) > 1) {
                int dif = abs(digSum(x) - digSum(y)) / 2;
                x -= dif;
                y += dif;
            }
            cout << x << " " << y << endl;
        }
    }
}

题目3

题目链接 题目大意: 给出n个整数的数组,从数组中选择两个数字a[i]和a[j],要求满足: i和j不相同; | a[i] - a[j] |的值,等于数组中最大值与最小值的差; 问,最多能选出多少个组合。

输入: 第一行,整数𝑡 表示t个样例𝑡 (1≤𝑡≤100) 每个样例2行,第一行整数 𝑛 (2≤𝑛≤1e5) 第二行整数 𝑎1,𝑎2,…,𝑎𝑛 (1≤𝑎𝑖≤1e5)

输出: 每个样例一行,输出满足要求的组合数。

Examples input 2 5 6 2 3 8 1 6 7 2 8 3 2 10 output 2 4

样例解释: 样例1,满足要求的有[1, 8]和[8, 1]; 样例1,满足要求的有[2, 10]和[10, 2],其中2可以为第2个数字、第5个数字;

题目解析: 因为选出来的数字要满足差的绝对值最大,那么必然是最大值和最小值的组合。 假设最小值x,最大值y,首先看x和y是否相同。 如果x==y,则看x的总数,答案就是A(n, 2),即是从n个整数中选择2个数字的组合排列数; 如果x!=y,那么结果就是x的数量与y的数量的乘积。

trick: 两个情况都可能超int32。

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
            }
            sort(a, a + n);
            if (a[0] == a[n - 1]) cout << n * 1LL * (n - 1) << endl;
            else {
                int x = 0, y = 0;
                while (a[x] == a[0]) {
                    ++x;
                }
                while (a[n - 1 - y] == a[n - 1]) {
                    ++y;
                }
                cout << x * 1LL * y * 2 << endl;
            }
        }
    }
}

题目4

题目链接 题目大意: 给出1到n的n个整数的数组,现在需要从数组里面选择新的数组,但是已知有m对整数是不能共存: 比如说整数2和4不能共存,则新的数组里面不能同时存在2和4,比如说整数数组[1,2,3,4,5],其中[2,3]和[3,4]都是合法的,[2,3,4]是不合法的; 现在想知道,能选出多少个子数组,里面不存在不合法的整数对。

输入: 第一行,整数𝑡 表示t个样例𝑡 (1≤𝑡≤2⋅1e4), 每个样例第一行整数 𝑛 and 𝑚 (2≤𝑛≤1e5, 0≤𝑚≤1e5) 接下来m行整数 𝑥𝑖 and 𝑦𝑖 (1≤𝑥𝑖,𝑦𝑖≤𝑛, 𝑥𝑖≠𝑦𝑖),表示两个不能相互相处的整数。

输出: 每个样例一行,输出满足要求的组合数。

Examples input 2 3 2 1 3 2 3 4 2 1 2 2 3 output 4 5 样例解释: 样例1,满足要求的有整数[1],[2],[3],[1,2]; 样例2,满足要求的有整数[1],[2],[3],[4],[3,4];

题目解析: 对于子数组[x, y],我们可以枚举做区间x,再考虑y的取值; 首先y要满足x <= y,考虑所有y的取值,只要包括一个不能相处整数对,那么后续的整数就不用考虑,比如说样例2: x=1,我们考虑y=1,2,3,4的情况,当我们发现整数1和2是不能相处的,那么2以及2后面的整数就不用考虑; 也即是说,我们只需要向右遍历到第一个出现的最小不能相处整数对的右区间y,[x, x]到[x, y-1]的区间都是合法的;

现在的问题变成了,对于选定整数x,如何快速得到右边区间最小的整数对; 假设我们把所有不合法整数对都按照左区间归类,然后从右到左遍历整数数组。 比如说遍历到整数i,那么对于所有做左区间≥i的整数对,都是在i的右边;

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int t;
        cin >> t;
        int tmp = 0;
        while (t--) {
            ++tmp;
            int n, m;
            map<int, vector<int> > mp;
            cin >> n >> m;
            while (m--) {
                int x, y;
                cin >> x >> y;
                if (x < y) {
                    mp[x].push_back(y);
                }
                else {
                    mp[y].push_back(x);
                }
            }
            lld sum = 0;
            int leftMin = n + 1;
            for (int i = n; i > 0; --i) {
                if (mp[i].size()) {
                    sort(mp[i].begin(), mp[i].end());
                    leftMin = min(leftMin, mp[i][0]);
                }
                sum += leftMin - i;
            }
            cout << sum << endl;
        }
    }
}

题目5

题目链接 题目大意: 给出n个整数,询问是否存在两个整数,最大公约数不是1; 如果存在则输出YES,如果不存在则输出NO;

输入: 第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e5); 每个样例第一行整数 𝑛 (2≤𝑛≤1e5). 接下来n个整数 𝑎1,𝑎2,…,𝑎𝑛 (1≤𝑎𝑖≤1e9).

输出: 每个样例一行,输出是否满足要求。

Examples input 2 3 32 48 7 3 14 5 9 output YES NO

题目解析: 先不考虑复杂度,最直接的做法,就是求出每个整数的因子,复杂度是O(M),M是整数的大小的根,10^9需要遍历1w+次; 然后用map管理每个整数的因子,遍历每一个整数求是否有相同的因子,整体复杂度在O(NM)左右,map的复杂度可以暂时忽略; 总的复杂度会到达10^5 * 10^5 = 10^10量级,时间上是不允许的。

分析这个里面的优化空间,每个整数的因子最终都可以拆分为若干素数,这样就可以大幅度减少因子数量; 先预处理1-sqrt(10^9)的素数,再用这个素数去筛选n个整数,这样速度会更加快。

trick:这个如果遇到两个相同数字,就会出现异常。 另外在拆解的时候,一定要拆解数字到x = a * b * c这样的形式。

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int m = sqrt(1e9) + 1;
        vector<int> vec;
        
        for (int i = 2; i<= sqrt(m); ++i) {
            if (!a[i]) {
                int tmp = i;
                while (tmp <= m) {
                    tmp += i;
                    a[tmp] = 1;
                }
            }
        }
        for (int i = 2; i <= m; ++i) {
            if (!a[i]) {
                vec.push_back(i);
//                cout << i << " ";
            }
        }
        
        int t;
        cin >> t;
        while (t--) {
            int n;
            map<int, int> mp;
            cin >> n;
            bool ok = 0;
            for (int i = 0; i < n; ++i) {
                int k;
                cin >> k;
                vector<int> tmp;
                for (int j = 0; j < vec.size(); ++j) {
                    if (k % vec[j] == 0) {
                        tmp.push_back(vec[j]);
                        while (k % vec[j] == 0) k /= vec[j];
                    }
                }
                if (k != 1) {
                    tmp.push_back(k);
                }
                
                for (int j = 0; j < tmp.size(); ++j) {
                    if (mp[tmp[j]]) ok = 1;
                    mp[tmp[j]] = 1;
                }
            }
            cout << (ok ? "YES" : "NO") << endl;
        }
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 题目1
  • 题目2
  • 题目3
  • 题目4
  • 题目5
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档