AcWing第45场周赛

题目列表

AcWing 4393. 字符串价值

题目描述

每个字符 1 的价值为 a1,每个字符 2 的价值为 a2,每个字符 3 的价值为 a3,每个字符 4 的价值为 a4。

一个字符串的价值等于其所包含的所有字符的价值之和。

给定一个由字符 1、2、3、4 构成的字符串 S,请你计算它的价值。

输入格式
第一行包含四个整数 a1,a2,a3,a4。

第二行包含一个字符串 S。

输出格式
一个整数,表示字符串 S 的价值。

数据范围
前三个测试点满足 0≤a1,a2,a3,a4≤5,1≤|S|≤10。
所有测试点满足 0≤a1,a2,a3,a4≤104,1≤|S|≤105

输入样例1:
1 2 3 4
123214
输出样例1:
13
输入样例2:
1 5 3 2
11221
输出样例2:
13

分析

遍历读入的字符串的同时加上该字符的权重,遍历结束得到的和就是字符串的价值。

代码

#include <iostream>
#include <string>
using namespace std;
int a[5];
int main(){
    for(int i = 1;i <= 4;i++)   cin>>a[i];
    string s;
    cin>>s;
    int res = 0;
    for(int i = 0;i < s.size();i++) {
        res += a[s[i] - '0'];
    }
    cout<<res<<endl;
    return 0;
}

AcWing 4394. 最长连续子序列

题目描述

给定一个长度为 n 的整数序列 a1,a2,…,an。

请你找出它的一个最长连续子序列,要求该子序列包含不超过 k 个不同的值。

输入格式
第一行包含两个整数 n,k。

第二行包含 n 个整数 a1,a2,…,an。

输出格式
共一行,两个整数 l,r,表示你找出的满足条件的最长连续子序列的最左端元素下标和最右端元素下标。

如果答案不唯一,输出任意合理方案均可。

数据范围
前 6 个测试点满足 1≤k≤n≤10。
所有测试点满足 1≤k≤n≤5×105,0≤ai≤106

输入样例1:
5 5
1 2 3 4 5
输出样例1:
1 5
输入样例2:
9 3
6 5 1 2 3 2 1 4 5
输出样例2:
3 7
输入样例3:
3 1
1 2 3
输出样例3:
1 1

分析

本题考察双指针,合法区间要满足的性质是区间内不重复的元素不能超过k个,初始时区间左端点和右端点都是1,随着元素的遍历,区间右端点不断右移,当区间内不重复元素超过k个时,区间左端点不断右移,直至区间内不重复元素不超过k个。从双指针的角度来看,问题的解区间随着问题规模的增大逐渐右移,解具有单调性。设区间的左端点是p,右端点是i,在i右移之前,以i为右端点的区间一定是满足本题元素个数不超过k条件下长度的最大值,所以当遍历完序列时,就相当于枚举了所有区间右端点的位置的合法区间的最大长度,其中的最大者就是问题的解了。
比如序列是1 2 3 4 5,k = 3时,当i = 1, 2,3时,左端点p = 1,当i = 4时,区间内不重复元素超过了3,左端点右移,p = 2,此时以i = 4为右端点的合法区间的最大长度就是3,左端点左移一位就不合法,右移一位区间长度就减小,这就保证了最终问题解的正确性。
最后需要考虑的是怎么维护区间内不重复元素的个数以及在区间移动时改变它的值。我们可以用个Hash表m来为区间内所有的元素计数,遍历到元素x时,如果m[x] == 0,就将不重复元素的个数cnt++,同时m[x]++;如果m[x] != 0,就直接m[x]++。如果cnt超过了k,就将区间左端点右移,右移过程中,m[a[p]]–,一旦a[p]的次数减到0,就将cnt–。

代码

#include <cstdio>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 500005;
unordered_map<int,int> m(N);
int a[N];
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    int p = 1,cnt = 0,ans = 0;
    pair<int,int> res;
    for(int i = 1;i<= n;i++) {
        scanf("%d",&a[i]);
        if(!m[a[i]]){//区间没有该元素
            while(cnt >= k) {//这里加上当前元素就超过k了,所有要保持之前的个数小于k
                m[a[p]]--;
                if(!m[a[p++]])    cnt--;
            }
            cnt++;
        }
        m[a[i]]++;//增加计数
        int s = i - p + 1;
        if(s > ans) {
            ans = s;
            res = {p,i};
        }
    }
    printf("%d %d\n",res.first,res.second);
    return 0;
}

AcWing 4395. 最大子矩阵

题目描述

给定一个长度为 n 的整数数组 a1,a2,…,an 和一个长度为 m 的整数数组 b1,b2,…,bm。

设 c 是一个 n×m 的矩阵,其中 ci,j=ai×bj。

请你找到矩阵 c 的一个子矩阵,要求:该子矩阵所包含的所有元素之和不超过 x,并且其面积(包含元素的数量)应尽可能大。

输出满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。

输入格式
第一行包含两个整数 n,m。

第二行包含 n 个整数 a1,a2,…,an。

第三行包含 m 个整数 b1,b2,…,bm。

第四行包含一个整数 x。

输出格式
一个整数,表示满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。

如果不存在满足条件的子矩阵,则输出 0。

数据范围
前三个测试点满足 1≤n,m≤5。
所有测试点满足 1≤n,m≤2000,1≤ai,bi≤2000,1≤x≤2×109

输入样例1:
3 3
1 2 3
1 2 3
9
输出样例1:
4
输入样例2:
5 1
5 4 2 4 5
2
5
输出样例2:
1

分析

本题虽然考察的算法不难,但是对问题的建模能力以及思维能力要求还是比较高的,相对来说是有一定难度的。
题目给定了两个序列,比如1 2 3 4和1 3 5,两个序列相乘可以得到一个矩阵,第一行是1 * (1,2,3,4),第二行是3 * (1,2,3,4),第三行是5 * (1,2,3,4),要求在矩阵里选一个子矩阵,使得这个子矩阵里面的元素和不超过x,并且元素个数尽可能的多。求矩阵的和自然可以使用二维前缀和,但是本题的矩阵比较特殊,也就是每行元素之间有倍数关系,所以使用移位前缀和就可以了。比如前面举例的矩阵,第一行元素和是1 + 2 + 3 + 4 = 10,第二行元素和就是3 * 10 = 30,第三行元素和是5 * 10=50,总的元素和就是(1 + 3 + 5) * 10 = 90,或者可以写成(1 + 2 + 3 + 4) * (1 + 3 + 5),也就是说由序列a上l1到r1元素与序列b上l2到r2元素相乘得到的矩阵之和就是(s[r1] - s[l1-1]) * (s[r2] - s[l2-1]),这个性质很容易想到。难的是想到如何高效的求出满足条件的子矩阵的最大面积。
还是拿之前的

1   2   3   4
3   6   9  12
5   10  15 20

这个矩阵为例,我们拿第一行的1, 2,3向下延伸子矩阵时,第一行和是6,延伸到第二行矩阵和变成了6 * (1 + 3),延伸到第三行变成了6 * (1 + 3 + 5);如果拿第一行的2, 3, 4向下延伸到第三行就变成了9 * (1 + 3 + 5),这两个子矩阵的面积都是9,但是其元素之和完全取决于第一行的元素和,没向下延伸一行,元素和就成倍增加,所以在选择相同长度的子矩阵时,我们优先选择和最少的那个向下延伸。
暴力求解本题的话,我们需要枚举第一个序列中长度为1,2, 3,…,n的所有子序列的和乘上第二个序列中所有长度为1,2,3,…,m的所有子序列和中不超过x的最大面积。但是我们知道了对于矩阵中第一行元素中相同长度的子序列,和越小越有优势,越可能向下延伸更多,所以我们只需要求出每个长度子序列的最小和即可,求出两个序列每个长度的最小序列和a[len]和b[len],就可以求出问题的解了。换而言之,题目要求的是子矩阵的最大面积,子矩阵有长有宽,求出了长为1,长为2,…,长为n的子矩阵的最大面积,取最大值就是问题的解了。
a和b分别是两个序列的每个长度的最小序列和构成的数组,比如题目给定第一个序列是1 2 3 4,那么a[1] = 1,a[2] = 3,因为所有长度为2的连续子序列里前两个元素之和最小,可见a数组是单调递增的,如果长度为2的子序列和小于序列长度为1的子序列和,那么长度为2的子序列中一定有比序列长度为1的那个元素更小的元素,与定义矛盾。b数组也是递增的,a和b数组的任意两个元素都可以构成一个子矩阵,比如a[3]和b[4]就构成了一个4 * 3的子矩阵,面积是15,和是a[3] * b[4],剩下要做的就是对每个a[i]求出符合题意的最大的j了,要求a[i] * b[j] <=x,并且j尽可能的大。这里类似两数之和问题,使用双指针求解,对于a[1],j首先指向b的末尾元素,如果末尾元素乘上a[1]大于x,则j–,直至符合条件,对于a[2]而言,由于其值大于a[1],符合条件的j一定不会在a[1]对应的j的右方,所以可以继续对j左移。
PS:本题的求解虽然描述起来略显复杂,但是看下代码还是很好理解的。

代码

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2005, INF = 1e9;
int a[N],b[N],s1[N],s2[N];
int main(){
    int n,m;
    cin>>n>>m;
    int x;
    for(int i = 1;i <= n;i++) {
        cin>>x;
        s1[i] = s1[i-1] + x;
    }
    for(int i = 1;i <= m;i++) {
        cin>>x;
        s2[i] = s2[i-1] + x;
    }
    cin>>x;
    for(int len = 1;len <= n;len++) {
        a[len] = INF;
        for(int i = 1;i + len - 1 <= n;i++) {
            a[len] = min(a[len],s1[i + len - 1] - s1[i-1]);
        }
    }
    for(int len = 1;len <= m;len++) {
        b[len] = INF;
        for(int i = 1;i + len - 1 <= m;i++) {
            b[len] = min(b[len],s2[i + len - 1] - s2[i-1]);
        }
    }
    int i = 1,j = m,res = 0;
    while(i <= n && j > 0) {
        while(j && a[i] > x / b[j])  j--;//这里如果不将乘法改为除法,可能会爆int
        res = max(res,i * j);
        i++;
    }
    cout<<res<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值