Educational Codeforces Round 69 Div2 A~D题解

本场链接:Educational Codeforces Round 69

闲话

感觉D非常难解释.就很奇怪.本场的C我的速度特别慢,忽视了一个条件导致一直没想出来.今天的晚上又是D2,希望能突破1400的上分吧.

A. DIY Wooden Ladder

原题大意:定义一个(k-)牛逼的积木,要求里面包含至少两个比(k+1)大或相等的木棍,和(k)个长度不为(0)的木棍.给定一些木棍,问最多能组成最大的(k-)牛逼的积木的是多少.
数据范围:
(1 leq n leq 10^5)

思路

直接列不等式即可.先求出所有木棍里最长的两个是谁,以次大值为(m),则需要满足(m geq k + 1).还要有(n-2 geq k)满足二条件.两者组合在一起取一个min即答案.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		map<int,int> tb;
		int n;scanf("%d",&n);
		int m1 = 0,m2 = 0;
		for(int i = 1;i <= n;++i)
		{
			int x;scanf("%d",&x);
			if(!m1)	m1 = x;
			else
			{
				if(x > m1)	m2 = m1,m1 = x;
				else if(x > m2)	m2 = x;
			}
		}

		printf("%d
",min(m2 - 1,n - 2));
    }
    return 0;
}

B. Pillars

原题大意:有(n)个柱子,初始时第(a_i)个柱子上存在一个圆盘.可以将相邻的两个柱子上的圆盘移动,具体移动规则是可以将半径小的放在半径大的上面,或者另一位是空的,并且可以叠加,即按最上面最小的当作整个圆盘集合的限制.但是你不可以把一摞圆盘一起移动,你只能移动最上方的,这个限制是让别的可以继续往上面摞而不是整体可以一起移动.问给定的圆盘序列能否通过若干次操作累在一个柱子上.
数据范围:
(3 leq n leq 2*10^5)
(1 leq a_i leq n)且两两不相同.

思路

显然如果序列里面存在一个波谷,则无解,因为必然会导致一边无法移动上去,即出现1 3在一起,但是外面存在一个2这样的圆盘,导致整个序列是不可能放在一起的.其次由于所有的半径其实就是一个排列,所以不存在两边相邻的情况,直接扫一遍判断就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 2e5+7;
int a[N];

int main()
{
	int n;scanf("%d",&n);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	int ok = 1;
	for(int i = 2;i <= n - 1;++i)
	{
		if(a[i - 1] > a[i] && a[i] < a[i + 1])
		{
			ok = 0;
			break;
		}
	}
	if(!ok)	puts("NO");
	else puts("YES");
    return 0;
}

C. Array Splitting

原题大意:给定一个有序的序列a,把他分成(k)段连续的子数组,每一段子数组计算一个权值,这个权值就是整段序列里的最大值减最小值.问分完(k)段之后,所有连续段的权值之和最小值是多少.
数据范围:
(1 leq k leq n leq 3*10^5)
(1 leq a_i leq 10^9,a_i >a_{i-1})

思路

显然求最大的最小值,很像是一个二分的框架,但是会发现这个题并不是整个的序列有一个权值,而是若干段总和在一起才有一个总体的去想办法让他最小的权值之和.所以二分是做不了的,因为check根本不能直接写这个判断.考虑dp或者贪心,可以发现这个题给了一个非常强的条件:即整个序列是有序的.这一点条件是很强的,因为整个序列如果不有序,那么想知道一段最大值和最小值是比较麻烦的,而且求的还是一个连续区间里的,所以这个条件必然是关键.
假如现在已经有一段([l,r])是分割的子段了,那么他的权值就很好计算,就直接是(a_r-a_l).假设已经分完了所有的序列,先不管具体(k)是多少,可以列出一个等式出来:(a_{k1}-a_1+a_{k2}-a_{k1+1} +a_{k3}-a_{k2+1} + ...... + a_n - a_{k - 1}).把这个表达是拆出来,可以找到一个特点:除了两个常数项(a_n - a_1),其他的各个元素都是形如(a_{k_i} - a_{k_i+1})的.也就是说,整个序列的权值之和,一部分常数项可以直接算出来,另一部分就是看删除位置(k)具体是谁,而他也很好算,就是删除的位置和下一位的差.进而可以想到直接贪心搞出来最小的(k-1)个形如(a_i - a_{i+1})的值,排序再算出总的答案,就做完了.
这题的数据范围很大,要注意防范爆int

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+7;
const ll INF = 1e16;
ll a[N];
int n,k;

int main()
{
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;++i)	scanf("%lld",&a[i]);
	vector<ll> q;
	for(int i = 1;i <= n - 1;++i)	q.push_back(a[i] - a[i + 1]);
	sort(q.begin(),q.end());
	ll res = a[n] - a[1];
	// for(auto& v : q)	cout << v << " ";cout << endl;
	for(int i = 0;i < k - 1;++i)	res += q[i];
	printf("%lld",res);
    return 0;
}

D. Yet Another Subarray Problem

原题大意:求(sumlimits_{i=l}^ra_i-klceildfrac{r-l+1}{m} ceil)的最大值.
数据范围:
(1 leq n leq 3*10^5)
(1 leq m leq 10)
(1 leq k leq 10^9)
(-10^9 leq a_i leq 10^9)

思路

对于一段区间来说,他的前半部分求和是很好算的,就是本来的序列.但是后面一半就很难算.不过这个题数据的一个特点就是(m)特别小,突破口肯定在这个(m)上.
对于一段序列,假如有(m)的长度,则会多减一个(k),这一点比较好想到,但是在这个基础上,由于后面的形式是上取整,所以对于前面,就是这一段(m)长度的区间来说,前面实际上也是比上一个区间的末尾多减一个(k)的,,这句话就是说,如果这个序列大概是这样减的:-k -k -k -2k -2k -2k -3k -3k -3k...,就是说你这一段,实际上不是到了最后一个元素再把减的元素增加一,而是一开始就加了.这一点我看很多博客都没有解释清楚.
由于(m)的取值特别小,对于这个题具体求解来说,可以按(m)余数来划分.具体来说,这个题的左端点假如说是(l),那么之后的(l+m,l+2m,l+3m....)都是确定的,就是可以确定端点是谁,进一步的,可以按对(m)取余的余数进行划分,就是枚举起点可能是非常多的,但是由于(m)的余数很少,所以可以把数量降下来,其实也就是只从第一位左端点往后推最大子序和.
想清楚这一点,这道题的思路也就比较明显了.先把整个序列上端点的位置减掉(k),再做一个最大子序和,从后往前加和,直到说有一个点跟起点枚举的余数是相同的,则更新答案.这个方案实际上是枚举的右端点,而对于答案统计的时候,也一定要走到一个余数相同的位置,因为前面虽然没有对各个元素减掉一个(k),但是实际前面实际的值都会因为上取整而去掉一个(k),不这样写就会导致结果的错误.
之所以只维护端点上的值而不去修改整个区间上的值(全都(-k)),是因为如果从前往后推最大子序和的时候,这样做会多次(-k),而如果只修改了端点值,则不会导致多次(-k),因为就一个端点减一次,一个端点就减一次,不会变多.但是这样做不能直接每一步更新答案,因为在之前位置上,也就是没走到端点的时候,是少减了一个(k)的,所以直到最后一位再谈更新答案的事.这样做的一个好处就是不用讨论端点取值左右端点的一些问题,但是理解起来会有一定的困难,网上大多数题解都随便说两句过去了,很明显不负责任,或者根本没想清楚人云亦云.
这个题还有一个(dp)解法,但是这两个做法本质相同,只不过说后者会好理解一点,不过这里就不展开了,如果这个做法会的话DP也可以自然写出来.本质上是一个背包模型.
数据量同样比较大,注意防范爆int

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+7;
int a[N],b[N];
int main()
{
	int n,m,k;scanf("%d%d%d",&n,&m,&k);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	ll res = 0;
	for(int i = 0;i < m;++i)
	{
		for(int j = 1;j <= n;++j)
		{
			b[j] = a[j];
			if(j % m == i) b[j] -= k;	
		}
		ll s = 0;
		for(int j = 1;j <= n;++j)
		{
			s = max(s + b[j],0ll);
			if(j % m == i)	res = max(res,s);
		}
	}
	printf("%lld",res);
    return 0;
}
原文地址:https://www.cnblogs.com/HotPants/p/13475556.html