CFEducational Codeforces Round 66题解报告
CFEducational Codeforces Round 66题解报告
感觉丧失了唯一一次能在CF上超过wqy的机会QAQ
A
不管
B
不能直接累计乘法打\(tag\),要直接跳
C
考虑二分第\(k\)小的值
那么问题就变成了
每一个数变成了\([x-mid,x+mid]\)的一段区间,如果有一个位置被覆盖了超过\(k\)次
那么\(mid\)一定合法
类似括号匹配
每次碰到左端点就贡献+1
右端点就统计答案然后-1
维护答案的同时顺便维护位置就好了
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<int,int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 4e5 + 3;
int a[N];
int n,k,ans;
vector <pii> G;
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
inline bool check(int mid){
G.clear();
for(int i = 1;i <= n;++i) G.push_back(mk(a[i] - mid,0)),G.push_back(mk(a[i] + mid,1));
sort(G.begin(),G.end());
int now = 0;
for(int i = 0;i < (int)G.size();++i){
if(G[i].se == 0) now++;
else{
if(now >= k){ans = G[i].fi;return 1;}
now--;
}
}
return 0;
}
int main(){
int T = read();
while(T--){
n = read(),k = read() + 1;
for(int i = 1;i <= n;++i) a[i] = read();
int l = 0,r = 1e9;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1;
else l = mid + 1;
}
printf("%d\n",ans);
}
return 0;
}
D
DP方法非常显然
但是时间复杂度不对
又没有凸性,无法优化(至少我不会)
只能考虑别的方法
发现每一次分段其实就是加上某一个后缀的值
很显然某个后缀只能加一次
所以题目变成了求\([2,n]\)的前\(k - 1\)大的后缀的和
最后将答案加上权值总和
#include<cstdio>
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<cctype>
#include<vector>
#include<ctime>
#include<cmath>
#define LL long long
#define pii pair<LL,LL>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int N = 5e5 + 3;
const LL INF = 1e15;
LL a[N];
LL sum[N];
LL maxx[N];
int n,k;
int tag[N];
priority_queue <pii> q;
inline int read(){
int v = 0,c = 1;char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') c = -1;
ch = getchar();
}
while(isdigit(ch)){
v = v * 10 + ch - 48;
ch = getchar();
}
return v * c;
}
int main(){
n = read(),k = read();
for(int i = 1;i <= n;++i) a[i] = read(),maxx[i] = -INF;
maxx[n] = sum[n] = a[n];
q.push(mk(sum[n],n));
for(int i = n - 1;i >= 1;--i){
// cout << i << endl;
sum[i] = sum[i + 1] + a[i];
maxx[i] = max(maxx[i + 1],sum[i]);
if(i != 1) q.push(mk(sum[i],i));
}
LL ans = sum[1];
//cout << "GG" << endl;
for(int i = 1;i < k;++i,q.pop()){
ans += q.top().fi;
}
cout << ans;
return 0;
}
F
方法一;考虑分治
题目中要求的区间转化一下就是下面两个条件
区间\([l,r]\)合法当且仅当
\(1\) 区间最大值为\(r - l + 1\)
\(2\) 区间无重复元素
我们考虑每次按照最大值去分治
考虑跨过最大值的贡献
接下来要满足第二个条件
我们设\(pre_i\)为\(a_i\)上一次出现的位置
\(max_i\)为\(pre_i\)的前缀max
发现区间\([l,r]\)无重复元素的意思是
\(max_r < l\)
就是每个数上一次出现的位置都在\(l\)左边
由于区间最大值就是区间长度
我们就可以通过枚举一边寻找另一边的方式求解
方法二;
我们给每一个数随机分配一个\(128\)位的数字
所以区间\([L,R]\)符合条件
就可以用前缀异或去表示
由于合法区间一定包含\(1\)
我们就从一个\(1\)开始到下一个\(1\)为止去寻找最大值在右边的贡献
之后把数组反过来再来一遍
这样\(1\)可能会被计算两边,特判就好。