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\)可能会被计算两边,特判就好。

posted @ 2019-08-20 21:17  wyxdrqcccc  阅读(126)  评论(0编辑  收藏  举报