Acwing 45场周赛

A 字符串的价值

题目

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

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

给定一个由字符 1234 构成的字符串 S,请你计算它的价值。

输入格式

第一行包含四个整数 a1,a2,a3,a4
第二行包含一个字符串 S。

输出格式

一个整数,表示字符串 S 的价值。

数据范围

前三个测试点满足 0≤a1,a2,a3,a4≤50,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

思路

模拟 数组记录下a1,a2,a3,a4对应的权值,扫描字符串进行对应的累加即可

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int a[5];

int main()
{
    string s;
    for (int i = 1; i <= 4; i ++ )       scanf("%d", &a[i]);
    
    cin >> s;
    
    int res = 0;
    for (int i = 0; i < s.size(); i ++ )
        res += a[s[i] - '0'];
        
    cout << res << endl;
    
    return 0;
}

B 最长连续子序列

题目

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

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

输入格式

第一行包含两个整数 n , k n,k n,k

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

输出格式

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

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

数据范围

前 666 个测试点满足 1 ≤ k ≤ n ≤ 10 1≤k≤n≤10 1kn10
所有测试点满足 1 ≤ k ≤ n ≤ 5 × 1 0 5 , 0 ≤ a i ≤ 1 0 6 1≤k≤n≤5×10^5, 0≤ai≤10^6 1kn5×105,0ai106

输入样例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

思路

知识点:双指针

  • 利用两个指针 i , j i, j i,j维护一个区间 [ j . . . i ] [j...i] [j...i],利用一个数组记录下区间内的出现了几次。
  • 从前往后扫描数组 a a a, 将新加入的数 a [ i ] a[i] a[i]出现次数 + 1 +1 +1,如果是第一次出现,则种类数量 + 1 +1 +1;
  • 当种类数量大于k时,需要将j指针前移,同时将 a [ j ] a[j] a[j]对应的出现次数 − 1 -1 1,当某个数减到 0 0 0时,说明区间里的种类数量 − 1 -1 1 j j j不断向前移动,直到区间里的种类数量小于等于 k k k.此时统计区间长度,更新答案。

代码

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 5e5 + 10, M = 1e6 + 10;

int a[N], cnt[M];
int n, k;

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++ )      scanf("%d", &a[i]);
    
    int kinds = 0;
    PII ans;
    
    int res = 0;
    for (int i = 1, j = 1; i <= n; i ++ )
    {
        if(cnt[a[i]] == 0)      kinds ++;  // a[i] 第一次出现, 种类数加1
        cnt[a[i]] ++;
        
        while(j < i && kinds > k)       // 使得区间内的种类数目小于k
        {
            cnt[a[j]] --;               
            if(cnt[a[j]] == 0)      kinds --;  // 种类减少
            j ++;
        }
        
        if(res < i - j + 1)     ans = {j, i}, res = i - j + 1;  // 更新答案
    }
    
    cout << ans.x << ' ' << ans.y;
    
    return 0;
}

C 最大子矩阵

题目

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

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

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

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

输入格式

第一行包含两个整数 n,m。

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

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

第四行包含一个整数 x。

输出格式

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

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

数据范围

前三个测试点满足 1 ≤ n , m ≤ 5 1≤n,m≤5 1n,m5
所有测试点满足 1 ≤ n , m ≤ 2000 , 1 ≤ a i , b i ≤ 2000 , 1 ≤ x ≤ 2 × 1 0 9 1≤n,m≤2000,1≤ai,bi≤2000,1≤x≤2×10^9 1n,m20001ai,bi20001x2×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

思路

贪心 + 前缀和 + 预处理 + 双指针/二分

  • 首先根据cij=ai×bj我们可以快速求出c子矩阵的和
    该矩阵的求和为 ( a 1 ∗ b 1 ) + ( a 1 ∗ b 2 ) + . . . . . + ( a 3 ∗ b 3 ) (a1 * b1) + (a1 * b2) + ..... + (a3 * b3) (a1b1)+(a1b2)+.....+(a3b3)
    整理后得到 ( a 1 + a 2 + a 3 ) ∗ ( b 1 + b 2 + b 3 ) (a1 + a2 + a3) * (b1 + b2 + b3) (a1+a2+a3)(b1+b2+b3)
    求子矩阵和就转化为了求两个数组 a , b a, b a,b的子区间的乘积
a1 * b1a1 * b2a1 * b3
a2 * b1a2 * b2a2 * b3
a3 * b1a3 * b2a3 * b3
  • 暴力想法:枚举a, b所有长度的区间,把他们相乘;在小于x的情况下统计子矩阵大小的最大值。但是a, b各有n2个区间,因此时间复杂度为n4, 那么如何减少枚举的个数?
  • 根据贪心的原则,在数组a中,每个区间长度固定的情况下,优先选择总和最小的才能保证最后的乘积尽可能的小,更有可能小于x, 还能选择b数组更长的区间,使得子矩阵更大。
  • 因此,枚举每一a, b数组每种长度区间,记录下当前长度下区间和的最小值。求区间和自然想到前缀和;之后枚举a数组每一种区间长度下的最小值,在b数组中找到小于使得二者乘积小于x的最大长度。
  • 每种区间长度下的最小值存入一个数组中,该数组是单调递增的,具体原因可以用反证法来证明。因此,在b数组中找到小于使得二者乘积小于x的最大长度可以用二分查找或者双指针

代码 (二分查找版)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2010, INF = 0x3f3f3f3f;

int sa[N], sb[N];
int mina[N], minb[N];
int n, m;
int target, res;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )      
    {
        int x;
        scanf("%d", &x);
        sa[i] = sa[i - 1] + x;   // 预处理a数组的前缀和
    }
    
    for (int i = 1; i <= m; i ++ )
    {
        int x;
        scanf("%d", &x);
        sb[i] = sb[i - 1] + x;  // 预处理b数组的前缀和
    }
    
    for (int len = 1; len <= n; len ++ )
    {
        mina[len] = INF;      // 一开始每种长度的区间和初始化为INF
        for (int i = 1; i + len - 1 <= n; i ++ )
        {
            int j = i + len - 1;
            mina[len] = min(mina[len], sa[j] - sa[i - 1]);  // 记录下a数组中每种长度下的最小区间和
        }
    }
    
    scanf("%d", &target);
    for (int len = 1; len <= m; len ++ )
    {
        minb[len] = INF;
        for (int i = 1; i + len - 1 <= m; i ++ )
        {
            int j = i + len - 1;
            minb[len] = min(minb[len], sb[j] - sb[i - 1]);  // 记录下b数组中每种长度下的最小区间和
        }
    }
    
    for (int len = 1; len <= n; len ++ )           // 枚举a数组每一种长度
    {
        int l = 0, r = m;   
        while(l < r)                            // 二分查找出使得a * b < x的最大矩阵
        {       
            int mid = (l + r + 1) >> 1;
            if(minb[mid] <= target / mina[len])   l = mid;  
            else                                r = mid - 1;
        }
        
        res = max(res, len * l);
    }
    
    printf("%d", res);
    
    return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2010, INF = 0x3f3f3f3f;

int sa[N], sb[N];
int mina[N], minb[N];
int n, m;
int target, res;

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )      
    {
        int x;
        scanf("%d", &x);
        sa[i] = sa[i - 1] + x;   // 预处理a数组的前缀和
    }
    
    for (int i = 1; i <= m; i ++ )
    {
        int x;
        scanf("%d", &x);
        sb[i] = sb[i - 1] + x;  // 预处理b数组的前缀和
    }
    
    for (int len = 1; len <= n; len ++ )
    {
        mina[len] = INF;      // 一开始每种长度的区间和初始化为INF
        for (int i = 1; i + len - 1 <= n; i ++ )
        {
            int j = i + len - 1;
            mina[len] = min(mina[len], sa[j] - sa[i - 1]);  // 记录下a数组中每种长度下的最小区间和
        }
    }
    
    scanf("%d", &target);
    for (int len = 1; len <= m; len ++ )
    {
        minb[len] = INF;
        for (int i = 1; i + len - 1 <= m; i ++ )
        {
            int j = i + len - 1;
            minb[len] = min(minb[len], sb[j] - sb[i - 1]);  // 记录下b数组中每种长度下的最小区间和
        }
    }
    
    for(int i = 1, j = m; i <= n; i ++)
    {
        while(j > 1 && mina[i] > target / minb[j])   j --;
        if(mina[i] <= target / minb[j])      res = max(res, i * j);
    }
    
    printf("%d", res);
    
    return 0;
}
```
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值