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

2023-05-20  本文已影响0人  落影loyinglin

题目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;
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读