zl程序教程

您现在的位置是:首页 >  Java

当前栏目

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

2023-02-18 16:33:36 时间

题目1

题目链接 题目大意: 给出一个数组长度为n,每次可以选择若干个数字,将其数字+1; 问最少需要操作几次,才能让整个数组内的元素值相等。

输入: 第一行,样例数? (1≤?≤1e4) 每个样例两行,第一行整数? (1≤?≤50) 第二行?1,?2,…,?? (1≤??≤1e9)

输出: 每个样例一行,输出最小的操作次数。

Examples input 3 6 3 4 2 4 1 2 3 1000 1002 998 2 12 11

output 3 4 1

题目解析: 由贪心的思想,由最大值减去最小值就能得到最多需要操作次数k; 因为其他数字和最大值的差距,不会比这个值更大,肯定可以在k次操作内完成。

class Solution {
    static const int N = 200010;
    string str;
    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];
            }
            int valMin = a[0], valMax = a[0];
            for (int i = 1; i < n; ++i) {
                valMin = min(valMin, a[i]);
                valMax = max(valMax, a[i]);
            }
            printf("%d\n", valMax - valMin);
        }
    }
}
ac;

题目2

题目链接 题目大意: 给出三个正整数a、b、c,现在可以对三个整数中的一个乘以正整数m; 这个操作只能执行一次,要求是最后三个成为等差数列。 比如说原来三个整数是10、5、30,那么可以将第二个乘以4,那么三个整数变为10、20、30,可以形成等差数列。

输入: 第一行样例数? (1≤?≤1e4) 每个样例一行,包括整数?, ?, ? (1≤?,?,?≤1e8). 输出: 如果有解,输出YES;如果无解,输出NO;

Examples input 11 10 5 30 30 5 10 1 2 3 1 6 3 2 6 3 1 1 1 1 1 2 1 1 3 1 100000000 1 2 1 1 1 2 2 output YES YES YES YES NO YES NO YES YES NO YES

题目解析: 最终想要形成的是等差数列,即是a-b=b-c; 由此可以推出来: new_a=2b-c; new_b=(a+c)/2; new_c=2b-a; 那么就只要看最终new_a、new_b、new_c是不是原来的a、b、c的整数倍即可。

trick: 需要注意,由于m是正整数,所以new_a必须大于等于a才行。

class Solution {
    static const int N = 200010;
    string str;
    int a[N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int a, b, c;
            cin >> a >> b >> c;
            int ok = 0;
            int new_a = 2 * b  - c, new_b = (a + c) / 2, new_c = 2 * b - a;
            if (new_a >= a && new_a % a == 0) {
                ok = 1;
            }
            if (new_b >= b && new_b * 2 == (a + c) && (new_b % b == 0)) {
                ok = 1;
            }
            if (new_c >= c && new_c % c == 0) {
                ok = 1;
            }
            cout << (ok ? "YES" : "NO") << endl;
        }
    }
}
ac;

题目3

题目链接 题目大意: 给出n个正整数的数组a,现在每次可以对数组的一个元素操作:a[i]=a[i]除以2,向下取整; 问经过若干次操作之后,能否将数组变成1到n的排列,即数组是由整数1到n组成。

输入: 第一行样例数? (1≤?≤1e4) 每个样例两行,第一行整数? (1≤?≤50) 第二行n个整数 ?1,?2,…,?? (1≤??≤1e9)

输出: 如果有解,输出YES;如果无解,输出NO;

Examples input 6 4 1 8 25 2 2 1 1 9 9 8 3 4 2 7 1 5 6 3 8 2 1 4 24 7 16 7 5 22 6 22 4 22 output YES NO YES NO NO YES

题目解析: 由贪心的思想可以知道,如果给出的整数a中存在1到n的整数,应该优先使用这些整数; 接着从剩下的整数中,不断挑选整数进行除2操作,如果遇到没有出现过的整数,则停止除2操作;

因为即使存在另外整数除以2会得到相同值的整数,在得到相同值的时候,他们已经是等价的整数了。

class Solution {
    static const int N = 10010;
    string str;
    int a[N];
    int cnt[N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
                cnt[i + 1] = 0;
            }
            int ok = 1;
            for (int i = 0; i < n; ++i) {
                while (a[i]) {
                    if (a[i] <= n && cnt[a[i]] == 0) {
                        cnt[a[i]] = 1;
                        break;;
                    }
                    a[i] /= 2;
                }
                if (!a[i]) {
                    ok = 0;
                }
            }
            cout << (ok ? "YES" : "NO") << endl;
        }
    }
}
ac;

题目4

题目链接 题目大意: 小明在玩游戏,游戏中有n个怪兽站成一排,每个怪兽的血量为a[i]; 小明可以释放技能,每次技能可以伤害其中1个怪兽2点血,以及左边或者右边相邻1点血; 比如说3个怪兽血量为[1,3,1],则小明可以对中间怪兽释放两次技能,则可以把所有怪兽的血量清零;(怪兽血量为0仍然可以释放技能) 现在只需要把其中2个怪兽的血量清理,问最少需要放几次技能;

输入: 第一行,整数n (2≤?≤2⋅1e5) 第二行,n个整数?1,?2,…,?? (1≤??≤1e6)

输出: 最少需要的技能次数;

Examples input 5 20 10 30 10 20 output 10

题目解析: 这个题目用dp来解决,dp[i][0]表示前i怪兽打死1个的最小次数,dp[i][1]表示前i个怪兽打死2个的最小次数; 对于第i个怪物,有打死和不打死两种可能,不打死的话直接dp[i-1]转移,打死的话有多种可能:单独打死,和i-1一起打死,或者和i-2一起打死; 根据上面两种情况,得到两个状态的转移方程,即可解决问题,复杂度O(N);

但是这个题目不需要动态规划,同样可以解决: 打怪兽只有两种可能,分开打死和一起打死,分开打死取数组最小2个元素即可,一起打死有下面两种可能: 1、两个怪兽是相邻的,可以假设血量为x和y(x<y),先只考虑对y放技能的情况,算出打死两个怪物的最少次数,然后考虑有多少个1/2的伤害可以替换为2/1; 2、两个怪兽是不相邻的,那么必然是打击中间的怪兽,掉两边的血;当有一个怪物死亡后,就可以瞄准剩下的怪物;(解决血量为[1,10,3]这样的情况) 复杂度同样为O(N),并且不要考虑状态转移,实现更加简单;

class Solution {
    static const int N = 201010;
    int a[N], dp[N][2];
    
    int check(int x, int y) {
        int maxItem = max(x, y);
        int minItem = min(x, y);
        int cnt = 0;
        if (minItem * 2 <= maxItem) {
            cnt = (maxItem + 1) / 2;
        }
        else {
            cnt = minItem - (minItem * 2 - maxItem) / 3;
        }
        return cnt;
    }

public:
    void solve() {
        int n;
        cin >> n;
        for (int i = 0; i < n; ++i) {
            cin >> a[i];
        }
        int ans = 0xfffffff;
        for (int i = 1; i < n; ++i) {
            ans = min(ans, check(a[i], a[i - 1]));
            if (i >= 2) {
                ans = min(ans, min(a[i], a[i - 2]) + (a[i] + a[i - 2] - min(a[i], a[i - 2]) * 2 + 1) / 2);
            }
        }
        sort(a, a + n);
        ans = min(ans, (a[0] + 1) / 2 + (a[1] + 1) / 2);
        cout << ans << endl;
    }
}
ac;

题目5

题目链接 题目大意: 给出一个n x m的矩阵,由' . '和' * '组成; 现在想把矩阵内的星号全部调整到前面,比如说:

.*. .** .**

调整为 **. **. *.. 先填充第1列,然后再填充第2列,以此类推,填充顺序为从上到下; 每次移动可以选择一个' * '移动到任意为' . '的位置;

现在有q次操作,每次会输入2个位置x和y,会把对应位置的元素修改为另外一个符号; 现在想知道每次操作之后,最少需要的移动次数;(每次操作的结果保留在矩阵中)

输入: 第一行,整数 ?, ? and ? (1≤?,?≤1000;1≤?≤2⋅1e5) 接下q行,每行2个整数 ?? and ?? (1≤??≤?;1≤??≤?)

输出: 输出q行,每行是操作后的最少移动次数;

Examples input

4 4 8
..**
.*..
*...
...*
1 3
2 3
3 1
2 3
3 4
4 3
2 3
2 2

output

 3
 4
 4
 3
 4
 5
 5
 5

题目解析: 按照题目的要求,假设矩阵中有sum个星号,容易知道最终排成的元素会集中在j=(sum-1)/n列和i=(sum-1)%m行中; 并且我们得到一个快速计算移动次数的方法,假如前j列,前i行中一共有ans个星号,那么最少移动次数就是sum-ans;

容易知道q次操作中,每次操作之后影响str[x][y]这个位置,并且会导致sum变化,从而导致ans变化; 但是由于每次操作只能修改1个字符,我们只需判断下sum变化之前,最后一个位置是否为星号,就可以快速计算得到ans; 这样每次操作之后,计算sum和ans的复杂度只需要O(1);

注意,每次操作之后,有两种可能会影响ans: 1、操作的字符就在最终排列的结果里面; 2、影响sum之后,sum关联到的对应的最后一个位置;

class Solution {
    static const int N = 1010;
    vector<string> str;
    
  
public:
    void solve() {
        int n, m, q;
        cin >> n >> m >> q;
        for (int i = 0; i < n; ++i) {
            string tmp;
            cin >> tmp;
            str.push_back(tmp);
        }
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (str[i][j] == '*') {
                    ++sum;
                }
            }
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m; ++j) {
                if (j * n + i < sum) {
                    if (str[i][j] == '*') {
                        ++ans;
                    }
                }
                else {
                    break;
                }
            }
        }
        while (q--) {
            int x, y;
            cin >> x >> y;
            --x;
            --y;
            if (str[x][y] == '*') {
                str[x][y] = '.';
                if (y * n + x < sum) {
                    --ans;
                }
                int j = (sum - 1) / n, i = (sum -  1) % n;
                if ((i != x || j != y) && str[i][j] == '*') {
                    --ans;
                }
                --sum;
            }
            else {
                str[x][y] = '*';
                ++sum;
                if (y * n + x < sum) {
                    ++ans;
                }
                int j = (sum - 1) / n, i = (sum -  1) % n;
                if ((i != x || j != y) && str[i][j] == '*') {
                    ++ans;
                }
            }
            cout << sum - ans << endl;
        }
    }
}
ac;