程序员进阶之算法练习(六十二)AK练习

正文

题目1

题目链接
题目大意:
小明有a个1元硬币,b个2元硬币;
小明想要购买一个商品,并且不想找零;
现在小明想知道自己无法给到最低价格是多少;

比如说1个1元硬币,1个2元硬币,最低价格就是4元;
比如说0个1元硬币,1个2元硬币,最低价格就是1元;(不能找零)

输入:
第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4)
每个样例一行,整数 𝑎𝑖 and 𝑏𝑖 (0≤𝑎𝑖,𝑏𝑖≤1e8)

输出:
每个样例一行,输出最低价格;

Examples
input
5
1 1
4 0
0 2
0 0
2314 2374

output
4
5
1
1
7063

题目解析:
如果有1元硬币,那么必然可以给到a+2*b价格内的所有整数;
如果没有1元硬币,那么1元就无法给到;

class Solution {
    static const int N = 201010;
    int a[N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int x, y;
            cin >> x >> y;
            cout << (x > 0 ? x+1+2*y : 1) << endl;
        }
    }
}
ac;

题目2

题目链接
题目大意:
小明有n种糖果,每种糖果分别有a[i]颗;
现在小明开始吃糖果,并且每次只吃剩下糖果数量最多的那种,如果有多种则可以任意选择一种数量最多的糖果;

小明想知道最终,能不能吃完所有糖果,并且满足没有连续2天吃到一样的糖果;

输入:
第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4)
每个样例两行,第一行整数 𝑛 (1≤𝑛≤2⋅1e5)
第二行是n个整数𝑎𝑖 (1≤𝑎𝑖≤1e9)

输出:
每个样例一行,如果可以输出YES,如果不行则输出NO;

Examples
input
4
aabbcc
aaab
aaa
abba

output
0
2
1
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;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
            }
            sort(a, a + n);
            bool ans = 0;
            if (n == 1) {
                ans = a[0] <= 1;
            }
            else {
                ans = (a[n - 1] - a[n - 2]) <= 1;
            }
            cout << (ans ? "YES" : "NO") << endl;
        }
    }
}
ac;

题目3

题目链接
题目大意:
字符串s可以成为偶字符串,如果字符串满足:
1、字符串的长度为偶数;
2、所有奇数位的字符𝑎[𝑖],满足𝑎[𝑖]=𝑎[𝑖+1];

比如说“aa”、“aabb”、“aabbcc”就是偶字符串,但是“aaa”、“aaab”、“abba”就不是偶字符串;
现在想知道,最少移除掉字符串s中多少个字符,可以使得字符串s变成偶字符串;

输入:
第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4)
每个样例一行,字符串 𝑠 (1≤|𝑠|≤2⋅105),

输出:
每个样例一行,输出最少移除的字符串;

Examples
input
4
aabbcc
aaab
aaa
abba

output
0
2
1
2

题目解析:

dp[i] 表示前i个字符,能找到的最长even字符串;
那么对于第i个字符,我们有两个选择:
取第i个字符,那么找到最近一个和第i个字符相近的位置k,我们有dp[i]=max(dp[i], dp[k-1] + 2);
不取第i个字符,那么有dp[i]=max(dp[i], dp[i - 1]);


class Solution {
    static const int N = 201010;
    char str[N];
    int dp[N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            cin >> (str + 1);
            int n = strlen(str + 1);
            int pos[26] = {0};
            for (int i = 1; i <= n; ++i) {
                dp[i] = dp[i - 1];
                int index = str[i] - 'a';
                if (pos[index]) { //
                    dp[i] = max(dp[i], dp[pos[index] - 1] + 2);
                }
                pos[index] = i;
            }
            cout << n - dp[n] << endl;
        }
    }
}
ac;

题目4

题目链接
题目大意:
给出n个整数的数组a,其中数组的元素绝对值满足 abs(a[i]) <= 2;
现在可以移除数组前面x个元素和后面y的个元素,求剩下的元素乘积尽可能的大;

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

输出:
每个样例一行,包括整数x和y,x和y分别表示:移除数组前面x个元素和后面y的个元素;
允许移除所有的元素,这样乘积为1;
如果有多个答案,输出任意一个;

Examples
input
5
4
1 2 -1 2
3
1 1 -2
5
2 0 -2 2 -1
3
-2 -1 -1
3
-1 -2 -2

output

0 2
3 0
2 0
0 1
1 0

题目解析:
题目的要求是乘积尽可能大,那么数字0首先被排除,因为0乘以任意数字都0,而移除所有元素的乘积结果都是1;
那么按照0,将数组切分成若干段,题目变成了在某一个子区间[left, right]中,寻找乘积最大的子区间;
假如区间[left, right]没有负数,或者有偶数个负数,那么这个区间所有数字的乘积就是最大的;
假如区间[left, right]有奇数个负数,那么肯定是去掉最左边的负数(假如位置是posLeft),或者去掉最右边的负数(假如位置是posRight);
这样就可以得到区间[left, right]的最大乘积;

由于乘积会比较大,这里只需要统计2和-2的数量即可,这个结果越大,则乘积越大。


class Solution {
    static const int N = 201010;
    int a[N];
    int ansTotal, ansLeft, ansRight;
    int pos[N], k;
    
    void find(int left, int right) {
        int total = 0, posLeft = 0, posRight = right;
        int cntTotal = 0, cntLeft = 0, cntRight = 0;
        for (int i = left; i <= right; ++i) {
            if (a[i] < 0) {
                ++total;
            }
            if (abs(a[i]) == 2) {
                ++cntTotal;
            }
            if (a[i] < 0 && !posLeft) {
                posLeft = i;
            }
            if (a[i] < 0) {
                posRight = i;
            }
        }
        for (int i = left; i <= posLeft; ++i) {
            if (abs(a[i]) == 2) {
                ++cntLeft;
            }
        }
        for (int i = posRight; i <= right; ++i) {
            if (abs(a[i]) == 2) {
                ++cntRight;
            }
        }
        if (total % 2 == 0) {
            if (cntTotal > ansTotal) {
                ansTotal = cntTotal;
                ansLeft = left;
                ansRight = right;
            }
        }
        else {
            if (cntLeft < cntRight) {
                cntTotal -= cntLeft;
                if (cntTotal > ansTotal) {
                    ansTotal = cntTotal;
                    ansLeft = posLeft + 1;
                    ansRight = right;
                }
            }
            else {
                cntTotal -= cntRight;
                if (cntTotal > ansTotal) {
                    ansTotal = cntTotal;
                    ansLeft = left;
                    ansRight = posRight - 1;
                }
            }
        }
    }

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            
            for (int i = 1; i <= n; ++i) {
                scanf("%d", &a[i]);
            }
            
            k = 0;
            pos[k++]= 0;
            for (int i = 1; i <= n; ++i) {
                if (a[i] == 0) { //
                    pos[k++] = i;
                }
            }
            pos[k++] = n + 1;
            ansTotal = 0;
            ansLeft = 1;
            ansRight = 0;
            for (int i = 1; i < k; ++i) {
                int l = pos[i - 1];
                int r = pos[i]; // (l, r)
                find(l + 1, r - 1);
            }
            cout << ansLeft - 1 << " " << (n - ansRight) << endl;
        }
    }
}
ac;

题目5

题目链接
题目大意:
给出一个n x n的矩阵,矩阵由数字0和1组成;
现在可以对矩阵进行下列操作:
1、将数组的每一行向上移动;
2、将数组的每一行向下移动;
2、将数组的每一列向左移动;
2、将数组的每一列向右移动;
这个操作是没有代价的,可以进行任意次;
然后还可以对矩阵中任何一个数字进行异或(XOR)操作,这个操作的代价是1;

现在想要把整个矩阵变成单元矩阵,问最小的代价是多少;
(单元矩阵是一个对角线为非零元素,其它元素为零的方形矩阵)

输入:
第一行,整数𝑡 表示t个样例 𝑡 (1≤𝑡≤1e4)
每个样例两行,第一行是整数𝑛 (1≤𝑛≤2000)
接下来是n x n的01矩阵;

输出:
每个样例一行,输出最小的代价。

Examples
input
4

3
010
011
100

5
00010
00001
10000
01000
00100

2
10
10

4
1111
1011
1111
1111
output
1
0
2
11

题目解析:
题目的要求是构造单元矩阵的代价最小,那么需要尽可能利用无代价的4个位移操作;
以简单的3阶矩阵来分析,对于矩阵
010
001
010
我们可以拼接4个矩阵,得到
010 010
001 001
010 010
----------
010 010
001 001
010 010

以左移一列为例,结果就是图中左上角的2-4列,如下:
0 100
0 010
0 100

按照这个思路,其实就是在4个n x n矩阵拼出来的大矩阵中,找到一个n x n子矩阵,并且斜对角线的1尽可能多;
那么就直接从每一行的第一列开始向右下角遍历,保持长度为n的斜对角线,存在尽可能多的1;
但是直接拼接4个矩阵去模拟,整体实现复杂度比较高;对于第i行第j列的元素a[i][j],右下角其实就是a[i+1][j+1],其中:
j是从1到n,因为每行的遍历都是从最左开始;
i可能会存在大于n的情况,此时可以对n取模,保证数组不越界;

假设对角线上最多的1数量为maxSize,矩阵中所有1的数量是totalSize;
那么答案就是ans=(total - maxSize) + (n - maxSize) 。

class Solution {
    static const int N = 3010;
    char a[N][N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            
            for (int i = 1; i <= n; ++i) {
                cin >> (a[i] + 1);
            }
            
            int total = 0, maxSize = 0;
            for (int i = 1; i <= n; ++i) {
                for (int j = 1; j <= n; ++j) {
                    total += ('1' == a[i][j]);
                }
                // 从a[i][1]开始,向右下角遍历
                int tmpSize = 0;
                for (int k = 0; k < n; ++k) {
                    int x = (i - 1 + k) % n + 1;
                    int y = 1 + k;
//                    cout << x << " " << y << endl;
                    if (a[x][y] == '1') {
                        ++tmpSize;
                    }
                }
                maxSize = max(maxSize, tmpSize);
            }
            cout << (total - maxSize) + (n - maxSize) << endl;
        }
    }
}
ac;

题目6

题目链接
题目大意:
给出由+和-组成的字符串,我们称字符串为平衡的字符串,假如+和-的字符是相同的;
现在可以对字符串执行操作,每次将两个相邻的-号合并为+号;
假如若干次操作之后,字符串变成了平衡的字符串,那么这个字符串可以称之为特殊的字符串;

现在给出长度为n的字符串,问字符串中有多少子串是特殊的;

输入:
第一行,整数𝑡 表示t个样例 (1≤𝑡≤500)
每个样例两行,第一行是整数 𝑛 (1≤𝑛≤3000)
第二行是字符串;

输出:
输出满足要求的子串数量;

Examples
input
5
3
+-+
5
-+---
4
----
7
--+---+
6
+++---
output
2
4
2
7
4

题目解析:
这是easy难度,题目给出来的范围比较小;
那么可以使用最暴力的办法,O(N*N)的复杂度,枚举所有字符串的子串;
再分别计算这个子串是否符合要求;
判断一个字符串是否是特殊的,可以遍历整个字符串中+和-的数量(假如总数是x和y);
如果x==y,或者x<y并且(y-x)%3==0 && (y-x)%3 < z,则子串满足要求。
统计x或者y是一个比较快的过程,用countx[i][j]表示区间[i, j]内的+数量,那么countx[i][j]可以由countx[i][j-1]来快速计算;y的计算同理;


class Solution {
    static const int N = 3010;
    char str[N];
    int a[N];
    int dp[N]; // dp[i] 前i个字符,以i结尾,并且x==y的子串数量
    int countx[N][N], county[N][N];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            cin >> (str + 1);
            // 如果x==y,或者x<y并且`(y-x)%3==0
            
            int ans = 0;
            for (int i = 1; i <= n; ++i) {
                for (int j = i; j <= n; ++j) {
                    if (str[j] == '+') {
                        countx[i][j] = countx[i][j - 1] + 1;
                        county[i][j] = county[i][j - 1];
                    }
                    else {
                        countx[i][j] = countx[i][j - 1];
                        county[i][j] = county[i][j - 1] + 1;
                    }
                    if (countx[i][j] == county[i][j] || ((countx[i][j] < county[i][j] && (county[i][j] - countx[i][j]) % 3 == 0))) {
                        ++ans;
                    }
                }
            }
            cout << ans << endl;
        }
    }
}
ac;

题目7

题目链接
题目大意:
给出由+和-组成的字符串,我们称字符串为平衡的字符串,假如+和-的字符是相同的;
现在可以对字符串执行操作,每次将两个相邻的-号合并为+号;假如若干次操作之后,字符串变成了平衡的字符串,那么这个字符串可以称之为特殊的字符串;

现在给出长度为n的字符串,问字符串中有多少子串是特殊的;

输入:
第一行,整数𝑡 表示t个样例
每个样例两行,第一行是整数𝑛
第二行是字符串;

输出:
输出满足要求的子串数量;

Examples
input
5
3
+-+
5
-+---
4
----
7
--+---+
6
+++---
output
2
4
2
7
4

题目解析:
这是hard难度,题目给出来的范围比较大,O(N*N)的复杂度枚举所有字符串的子串的方式已经不适用;
首先需要对我们已经采用的方法进行简化,countx和county其实可以简化为countx-county的方式,并且由二维简化为一维:
我们用sum[i]表示前i个字符相加的结果,字符+表示-1,字符-表示+1;
那么子串[i, j]可以用sum[j] - sum[i - 1]的方式快速得到结果,(sum[j] - sum[i - 1])%3==0 && sum[j] >= sum[i - 1] 表示有解;

从左到右遍历字符串,对于第j个字符,首先得到sum[j],然后遍历i∈[1, j-1]判断 (sum[j] - sum[i - 1])%3==0 && sum[j] >= sum[i - 1] 的数量;
这个遍历过程存在较多重复计算,我们只在乎i∈[1, j-1]这个区间中sum[i]的值,而对i的位置并不关心,并且sum[i]的值最多也只有2n+1种可能(取值范围是[-n, n]);
那么如果我们把sum[i]的结果直接存到[-n, n] 这个区间上,我们就可以直接获取遍历这个区间的值即可。
以n=4为例,sum[i]的结果可能有[-4,-3,-2,-1,0,1,2,3,4],如果sum[j]=3,那么我们只需要取sum[i]=3/0/-3的值;如果sum[j]=2,那么我们只需要取sum[i]=-1/-4的值;
由于我们只在乎%3的结果,我们可以用cnt[0,1,2]来表示sum[i]%3的值和,这样从数组中取值就不需要遍历,可以很快取到某个余数的值;

但是这里还有一个条件是sum[j] >= sum[i - 1],sum[j]=0的时候,sum[i]=3这种情况是不允许的,所以cnt的值是需要维护的,维护方式就是:
当我们sum[j]变小的时候,cnt作为累加和,要退掉之前累加的值;
比如说sum[j]=2的时候,cnt累加了[-4, 2]的区间;sum[j+1]=1的时候,cnt累加了[-4, 1]的区间;也就是cnt需要退掉sum[j]=2的值;

由于sum[i]的取值范围是[-n, n] 我们可以将sum[i]+n,这样取值范围变成[0, 2n],好处是可以用数组来表示,并且不影响我们对结果的结算,因为 (sum[j] - sum[i - 1])等于 (sum[j] + n)-(sum[i-1] + n),sum[j] >= sum[i-1] 等于 sum[j] + n >= sum[i-1] + n;


class Solution {
    static const int N = 200010;
    char str[N];
    int sum[N * 2];
    int cnt[3];

public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            cin >> (str + 1);
            lld ans = 0;
            int preCount = n;
            memset(cnt, 0, sizeof(cnt));
            memset(sum, 0, sizeof(sum));
            sum[preCount] = 1;
            cnt[preCount % 3] = 1;
            for (int i = 1; i <= n; ++i) {
                int curCount = preCount + (str[i] == '-' ? 1 : -1);
                int index = curCount % 3;
                if (curCount < preCount) {
                    cnt[preCount % 3] -= sum[preCount];
                }
                else {
                    cnt[curCount % 3] += sum[curCount];
                }
                ans += cnt[index % 3];
                cnt[curCount % 3]++;
                sum[curCount]++;
                preCount = curCount;
            }
            cout << ans << endl;
        }
    }
}
ac;

总结

趁着假日闲暇时光以及工作间隙,把1660CF的7个题目都AC了。
有一丝成就感,毕竟已经是退役接近十年的选手;也有一丝挫败感,在毕业多年之后,水平已经变成只能在DIV3达成AK成就。
在最应该学习的时候,没有努力学习,那么未来即使有机会也很难超越自己。当年不擅长做的数论题,现在依旧不会做。
这让我逐渐明白一个道理:面对困难和挑战,人在当下的选择,往往会影响后续一辈子。
不是说生活一定要迎难而上,一定要吃得苦中苦方为人上人,而是思考和确定自己的目标、选择、动力,然后再努力前行。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容