第一题:周赛最高分
在线测评链接:http://121.196.235.151/p/P1153
题目描述
ak机是leetcode周赛的忠实玩家,为了防止周赛掉分和刷新knight的分数线,他总共创建了 n n n个账号,每个账号的分数分别为 a i a_i ai,现在我们记录了他 m m m次的比赛记录,ak机每次都会使用分数最低的账号参赛,请问ak机每次参赛后,他的所有账号的最大得分是多少
输入描述
输入包含三行
第一行两个正整数 n , m ( l ≤ n , m ≤ 1 0 5 ) n,m(l≤n,m≤10^5) n,m(l≤n,m≤105),分别表示ak机的账号个数和ak机新参加的比赛记录数。
第二行 n n n个整数 a i ( 0 ≤ a i ≤ 1 0 9 ) a_i(0\le a_i\le 10^9) ai(0≤ai≤109),表示ak机每个账号目前的分数。
第三行 m m m 个整数 b j ( 0 ≤ b j ≤ 1 0 9 ) b_j(0\le b_j\le 10^9) bj(0≤bj≤109),分别表示ak机每次比赛后,分数的变化值
(例如如果ak机使用分数为 x x x的账号参赛,那么他在参加完第 j j j场数的变化值。比赛后,该账号分数会变为 x + b j x+b_j x+bj。)
输出描述
输出包含 m m m行,每行一个整数,表示ak机参与完第 j j j场比赛后,他的所有账号的最大得分
样例
输入
5 4
1145 1200 1300 1500 1600
10 270 450 500
输出
1600
1600
1650
1800
解释
第一次和第二次询问都加在1145上,数组变成 [1425,1200,1300,1500,1600],第三次和第四次分别加在1200和1300上
题解:模拟 小根堆
由于每一次都是数组中的最小值和 b j b_j bj操作,因此我们可以维护一个小根堆来存储他的动态分数,那么每次操作只需要弹出小根堆的堆顶元素,并将值加上 b j b_j bj,然后将其与数组中最大值取 m a x max max即可。
本题C++和Java选手需要注意:数据范围较大,需要开long long
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
long long n,m,a[N],b[N];
int main(){
cin>>n>>m;
priority_queue<long long,vector<long long>,greater<long long>>heap; //小根堆
long long res=0;
for(int i=0;i<n;i++){
cin>>a[i];
heap.push(a[i]);
res=max(res,a[i]);
}
for(int i=0;i<m;i++){
cin>>b[i];
long long score=heap.top();heap.pop();
res=max(res,score+b[i]);
heap.push(score+b[i]);
cout<<res<<endl;
}
return 0;
}
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
PriorityQueue<Long> heap = new PriorityQueue<>(); // 小根堆
long res = 0;
long[] a = new long[n];
for(int i = 0; i < n; i++) {
a[i] = scanner.nextLong();
heap.add(a[i]);
res = Math.max(res, a[i]);
}
long[] b = new long[m];
for(int i = 0; i < m; i++) {
b[i] = scanner.nextLong();
long score = heap.poll();
res = Math.max(res, score + b[i]);
heap.add(score + b[i]);
System.out.println(res);
}
}
}
python
import heapq
n, m = map(int, input().split())
heap = [] # 小根堆
res = 0
a = list(map(int, input().split()))
for i in range(n):
heapq.heappush(heap, a[i])
res = max(res, a[i])
b = list(map(int, input().split()))
for i in range(m):
score = heapq.heappop(heap)
res = max(res, score + b[i])
heapq.heappush(heap, score + b[i])
print(res)
第二题:数组的删除方案
在线测评链接:http://121.196.235.151/p/P1154
题目描述
ak机有一个长度为
n
n
n的数组
a
a
a,他想要使得数组
a
a
a有序(单调不降),他必须选择一段区间
[
l
,
r
]
(
1
≤
l
,
r
≤
n
)
[l,r](1\le l,r\le n)
[l,r](1≤l,r≤n),将数组的这一段删除,其他的部分(如果存在的话)就按顺序拼在一起。
现在他想知道有多少种不同的选择区间的方案。
输入描述
第一行一个正整数 n ( 1 ≤ n ≤ 2 × 1 0 5 ) n(1\le n\le 2\times 10^5) n(1≤n≤2×105),表示数组的长度。
第二行 n n n个正整数 a i ( 1 ≤ a i ≤ 1 0 9 ) a_i(1\le a_i\le 10^9) ai(1≤ai≤109),表示数组 a a a。
输出描述
输出一行一个正整数表示答案。
样例
输入
3
1 2 3
输出
6
说明
可以选择:
[1, 1], [2, 2], [3, 3], [1, 2], [2, 3], [1, 3]
这六个区间
思路:双指针+二分查找
首先,我们考虑一种极端情况,就是整个数组本来就是一个单调非降数组,比如 [ 1 , 2 , 3 ] [1,2,3] [1,2,3],那么删除任意一个子区间都可以,因此对应的方案数记为子区间个数,等于 n × ( n + 1 ) 2 \frac{n\times (n+1)}{2} 2n×(n+1)
对于一般的情况,比如 [ 1 , 3 , 4 , 3 , 5 , 2 , 2 , 5 ] [1,3,4,3,5,2,2,5] [1,3,4,3,5,2,2,5] 左侧则为 [ 1 , 3 , 4 ] [1,3,4] [1,3,4],右侧则为 [ 2 , 2 , 5 ] [2,2,5] [2,2,5]
可以证明,中间的 [ 3 , 5 ] [3,5] [3,5]总会出现在删除方案中,也就是选择的 [ l , r ] [l,r] [l,r]区间一定包含 [ 3 , 5 ] [3,5] [3,5]
我们首先找到左侧的单调最长子数组 l e f t left left和右侧的单调最长子数组 r i g h t right right
有以下几种删除方式
- 第一种删除方式:选择一侧,删除另一侧的全部数字,比如选择左侧
保留[1,3,4]对应删除[3,5,...]
保留[1,3] 对应删除[4,3,5...]
保留[1] 对应删除[3,4,3,5...]
另一侧同理可得,选择一侧的删除方案数为 l e f t . s i z e ( ) + r i g h t . s i z e ( ) left.size() + right.size() left.size()+right.size()
- 第二种删除方式:两侧中都保留若干个数字,删除中间段,比如
左侧保留[1],右侧可以保留[2,2,5]、[2,5]、[5],则相应删除[3,4,3,5]、[3,4,3,5,2]..
左侧保留[1,3],右侧则可以保留[5]
由于是递增子数组,因此可以使用二分查找到另一侧保留的位置,进而得出有几种删除方案。
C++
#include<bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int n,a[N];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
a[0]=0;a[n+1]=INT_MAX;
int i=1,j=n;
while(i<=n&&a[i]>=a[i-1]){ //找到左侧的最长上升子数组
i++;
}
while(j>=1&&a[j]<=a[j+1]){ //找到右侧的最长上升子数组
j--;
}
i--;j++; //定位到最后一个上升的位置
if(i>j){ //说明整个数组就是一个非递减数组,删除任意区间都满足条件
long long res=1ll*n*(n+1)/2;
cout<<res<<endl;
}
else{
long long res=i+n-j+1; //删除左侧的递增数组or右侧的递增数组
res++; // 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
for(int pos=1;pos<=i;pos++){ //枚举左侧上升子数组的每一个位置
int l=j,r=n;
while(l<r){
int mid=l+r>>1;
if(a[mid]>=a[pos])r=mid;
else l=mid+1;
}
if(a[l]>=a[pos]){ //子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
res+=n-l+1;
}
}
cout<<res<<endl;
}
return 0;
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] a = new int[n + 2];
a[0] = 0;
a[n + 1] = Integer.MAX_VALUE;
for (int i = 1; i <= n; i++) {
a[i] = scanner.nextInt();
}
int i = 1, j = n;
while (i <= n && a[i] >= a[i - 1]) { // 找到左侧的最长上升子数组
i++;
}
while (j >= 1 && a[j] <= a[j + 1]) { // 找到右侧的最长上升子数组
j--;
}
i--;
j++; // 定位到最后一个上升的位置
if (i > j) { // 说明整个数组就是一个非递减数组,删除任意区间都满足条件
long res = 1L * n * (n + 1) / 2;
System.out.println(res);
} else {
long res = i + n - j + 1; // 删除左侧的递增数组or右侧的递增数组
res++; // 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
for (int pos = 1; pos <= i; pos++) { // 枚举左侧上升子数组的每一个位置
int l = j, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (a[mid] >= a[pos]) {
r = mid;
} else {
l = mid + 1;
}
}
if (a[l] >= a[pos]) { // 子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
res += n - l + 1;
}
}
System.out.println(res);
}
}
}
Python
n = int(input())
a = list(map(int, input().split()))
a.insert(0, 0)
a.append(2 * 10 ** 9)
i = 1
j = n
while i <= n and a[i] >= a[i - 1]: # 找到左侧的最长上升子数组
i += 1
while j >= 1 and a[j] <= a[j + 1]: # 找到右侧的最长上升子数组
j -= 1
i -= 1
j += 1 # 定位到最后一个上升的位置
if i > j: # 说明整个数组就是一个非递减数组,删除任意区间都满足条件
res = n * (n + 1) // 2
print(res)
else:
res = i + n - j + 1 # 删除左侧的递增数组or右侧的递增数组
res += 1 # 特殊情况,数组可以全部删除,这里题目中没有讲,只能在样例中看到
for pos in range(1, i + 1): # 枚举左侧上升子数组的每一个位置
l = j
r = n
while l < r:
mid = (l + r) // 2
if a[mid] >= a[pos]:
r = mid
else:
l = mid + 1
if a[l] >= a[pos]: # 子数组[1,pos]与[l,l],[l,l+1]...可以合并在一起,总共有n-l+1种方案
res += n - l + 1
print(res)
第三题:最长的R区间
在线测评链接:http://121.196.235.151/p/P1028
题目描述
给定长度为 n n n的一个字符串(仅由 R R R和 W W W组成),你需要把字符串中的所有字符都修改为 R R R,每一次修改只能将连续的 k k k个字符修改成 R R R,最大修改次数为 m m m,求 k k k的最小值
输入描述
第一行输入两个正整数 n , m ( 1 ≤ n ≤ 2 × 1 0 5 ) n,m(1\le n\le 2\times 10^5) n,m(1≤n≤2×105)
第二行输入长度为 n n n的字符串
输出描述
输出一个整数,表示 k k k的最小值
样例
输入
5 2
WRWWWR
输出
3
思路:二分答案
首先可知, k k k的值越大,每次修改的区间长度就越大,那么对应的修改次数就越少,就越容易满足修改次数 ≤ m \le m ≤m的条件
反之,则越不容易满足题目条件,因此是具有二段性的
因此我们可以对答案使用二分查找来枚举,当我们枚举到 k k k的值为 x x x时
我们使用贪心的思想进行check
,当我们在位置
i
i
i遇到
W
W
W字符的时候,我们就使用一次修改次数,然后就可以把整个区间
[
i
,
i
+
x
−
1
]
[i,i+x-1]
[i,i+x−1]都变成
R
R
R,如果最终修改完所有字符串,消耗的次数
≤
m
\le m
≤m,则说明当前答案满足条件,否则不满足条件,我们按照二分查找的方式对区间进行缩小
- 如果最终修改完字符串,消耗的次数 c n t ≤ m cnt\le m cnt≤m,则说明当前答案满足条件,可以将区间缩小至 [ l , x ] [l,x] [l,x]
- 如果最终修改完字符串,消耗的次数 c n t > m cnt> m cnt>m,则说明当前答案不满足条件,可以将区间缩小至 [ x + 1 , r ] [x+1,r] [x+1,r]
C++
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
string s;
bool check(int x){
int cnt=0;
for(int i=0;i<n;i++){
if(s[i]=='W'){
cnt++;
i+=x-1;
}
}
return cnt<=m;
}
int main()
{
cin>>n>>m;
cin>>s;
int l=1,r=1e9;
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<r<<endl;
return 0;
}
Java
import java.util.*;
public class Main {
static int n, m;
static String s;
static boolean check(int x) {
int cnt = 0;
for (int i = 0; i < n; i++) {
if (s.charAt(i) == 'W') {
cnt++;
i += x - 1;
}
}
return cnt <= m;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
s = scanner.next();
int l = 1, r = (int)1e9;
while (l < r) {
int mid = l + (r - l) / 2;
if (check(mid)) r = mid;
else l = mid + 1;
}
System.out.println(r);
}
}
Python
def check(x):
cnt = 0
i = 0
while i < n:
if s[i] == 'W':
cnt += 1
i += x
else:
i += 1
return cnt <= m
n, m = map(int, input().split())
s = input()
l, r = 1, int(1e9)
while l < r:
mid = l + (r - l) // 2
if check(mid):
r = mid
else:
l = mid + 1
print(r)