ACWing 第 45 场周赛
A、字符串价值
每个字符 1
的价值为 a1,每个字符 2
的价值为 a2,每个字符 3
的价值为 a3,每个字符 4
的价值为 a4。
一个字符串的价值等于其所包含的所有字符的价值之和。
给定一个由字符 1
、2
、3
、4
构成的字符串 SS,请你计算它的价值。
输入格式
第一行包含四个整数 a1,a2,a3,a4。
第二行包含一个字符串 S。
输出格式
一个整数,表示字符串 SS 的价值。
数据范围
前三个测试点满足 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
思路分析:
直接开个长度为4的数组,存一下每个字符的代价,然后遍历一遍字符串相加即可。
代码:O(n)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int>PII;
const int INF = 1e9;
const int N = 1e5 + 5;
ll val[5], ans;
string s;
int main()
{
for(int i = 1; i <= 4; i ++)cin>>val[i];
cin>>s;
for(int i = 0; i < s.size(); i ++){
ans += val[s[i] - '0'];
}
cout<<ans<<'\n';
return 0;
}
B、最长连续子序列
给定一个长度为 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
思路分析:
一开始思路就认为是双指针,但在写的时候用主指针(for中的变量)i去指向首端点,然后用第二个指针j去找能够到达的最长长度(即指向尾端点),如果维护区间的不同数大于k就退出。然后会遇到很多的问题,不好解决。具体问题就是j指针在维护区间内不同数大于k的时候还会后移一位。
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 1e9;
const int N = 1e5 + 5;
map<int, int>mp;
int n, k, l, r, len;
int a[N];
int main()
{
cin>>n>>k;
for(int i = 1; i <= n; i ++)cin>>a[i];
int i = 1, j = 1;
int cnt = 0;
for(; i <= n && j <= n; i ++){
if(cnt > k){
mp[a[i]]--;
if(mp[a[i]] == 0)cnt --;
if(cnt > k)continue;
}
while(cnt <= k && j <= n){
if(!mp[a[j]]){
mp[a[j]]++;
cnt++;
}
else {
mp[a[j]]++;
}
j++;
}
if(len <= j - i - 1){
len = j - i - 1;
l = i;
r = j - 2;
}
}
if(len < j - i){
len = j - i;
l = i;
r = j - 2;
}
cout<<l<<" "<<r<<'\n';
return 0;
}
/*
记录一下每个点是否被加入
如果相同的点的数量超过 k 就将i指针往后移动
取max区间长度即可
*/
但如果把思路反一下,用指针i去指向维护区间尾段,指针j指向维护区间首端,当维护的区间内部的不同数字大于k的时候,就移动j指针,直到维护的区间内不同数字的数量小于等于k。
代码:O(n)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 1e9;
const int N = 1e6 + 5;
int n, k, l, r, len;
int a[N], cnt[N]; //a数组:存放原数组 cnt:存放每个数在维护区间内出现的次数
int main()
{
cin>>n>>k;
for(int i = 1; i <= n; i ++)cin>>a[i];
int i , j, t = 0;
//i指针:指向维护区间的首段
//j指针:指向维护区间的末端
// t:维护区间内不同数的个数
for(i = 1, j = 1; i <= n; i ++){
if(cnt[a[i]] == 0)t ++; //a[i]数第一次出现,那么维护区间中不同数的个数 + 1
cnt[a[i]]++;
if(t > k) //维护区间内的不同书的个数大于k,要移动首端点来维护我们的区间
{
//如果长度当前最长,通过l,r变量记录首尾变量
if(len < i - j){
len = i - j;
l = j;
r = i - 1;
}
//移动指向首的指针,维护区间内不同数的个数小于等于k
while(t > k ){
if(cnt[a[j]] == 1)t--;
cnt[a[j]]--;
j++;
}
}
}
//最后还要进行以此判断
if(len < i - j){
len = i - j;
l = j;
r = i - 1;
}
cout<<l<<" "<<r<<'\n';
return 0;
}
C、 最大子矩阵
给定一个长度为 nn 的整数数组 a1,a2,…,an 和一个长度为 mm 的整数数组 b1,b2,…,bm。
设 cc 是一个 n×m 的矩阵,其中 ci,j=ai×bj。
请你找到矩阵 c 的一个子矩阵,要求:该子矩阵所包含的所有元素之和不超过 x,并且其面积(包含元素的数量)应尽可能大。
输出满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
输入格式
第一行包含两个整数 n,m。
第二行包含 n 个整数 a1,a2,…,an。
第三行包含 mm 个整数 b1,b2,…,bm。
第四行包含一个整数 xx。
输出格式
一个整数,表示满足条件的子矩阵的最大可能面积(即包含元素的最大可能数量)。
如果不存在满足条件的子矩阵,则输出 00。
数据范围
前三个测试点满足 1≤n,m≤5。
所有测试点满足 1≤n,m≤2000,1≤ai,bi≤2000,1≤x≤2×10^9。
输入样例1:
3 3
1 2 3
1 2 3
9
输出样例1:
4
输入样例2:
5 1
5 4 2 4 5
2
5
输出样例2:
1
思路分析:
根据题目给的数据大小,我们要把代码的时间复杂度控制在O(n^2)内。
对于题目要求由a数组和b数组构成的c矩阵内取的子矩阵最大且子矩阵数的乘积和小于等于x,根据c矩阵的组成,我们可以把问题转换成在a数组中取一段长度,同样,在b数组中也取一段长度,这两段的长度的乘积小于等于x且取的长度最长。
代码:O(n^2)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int INF = 1e9;
const int N = 2010;
int n, m, x;
int a[N], b[N], s1[N], s2[N], lena[N], lenb[N];
int main()
{
cin>>n>>m;
//求a数组的前缀和数组s1
for(int i = 1; i <= n; i ++){
cin>>a[i];
s1[i] = s1[i - 1] + a[i];
}
//求a数组的前缀和数组s2
for(int i = 1; i <= m; i ++){
cin>>b[i];
s2[i] = s2[i - 1] + b[i];
}
//求a数组中对应长度len能够取得的最小值(因为要让长度最大且小于x,所以要去长度大且小的)
for(int len = 1; len <= n; len ++){
lena[len] = INF;
for(int j = 1; j + len - 1 <= n; j ++){
lena[len] = min(lena[len], s1[j + len - 1] - s1[j - 1]);
}
}
//求b数组中对应长度len能够取得的最小值(因为要让长度最大且小于x,所以要去长度大且小的)
for(int len = 1; len <= m; len ++){
lenb[len] = INF;
for(int j = 1; j + len - 1 <= m; j ++){
lenb[len] = min(lenb[len], s2[j + len - 1] - s2[j - 1]);
}
}
cin>>x;
int ans = 0;
//因为乘积是随着面积的增大严格单调递增的,所以我们开始让a数组取得的长度最小
//让b数组取得长度最大,这样随着指针i指向a的长度的增大,指针j指向b的长度会随着降低
//这样双指针找一下,找出满足乘积小于等于x前提下的最大面积即可。
for(int i = 1, j = m; i <= n; i ++){
while(j && lena[i] > x / lenb[j])j--;
ans = max(ans, i * j);
}
cout<<ans<<'\n';
return 0;
}