Educational Codeforces Round 93 Div2 A~E题解

闲话

本场出了ABCD,因为思路不正确卡了很久D.非常的无语.

A. Bad Triangle

题目大意:给定(n)个元素的不降序列,找出三个不同位置的元素使他们三个不能组成一个三角形.

思路

直接拿最小的两个和最大的一个拼就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5+7;
ll a[N];
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int n;scanf("%d",&n);
		for(int i = 1;i <= n;++i)	scanf("%lld",&a[i]);
		if(a[1] + a[2] > a[n])	puts("-1");
		else	printf("1 2 %d
",n);
    }
    return 0;
}

B. Substring Removal Game

题目大意:两个人在玩一个游戏,这个游戏在一个字符串s上进行.每次可以从字符串里取出任意长的一段连续的相同的子串.得分是取出的1的个数,问先手的人最多可以有多少分.

思路

由于位置不固定,所以直接找到所有的连续的1的序列再按长度排序,隔着选就可以了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 205;
char s[N];
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		scanf("%s",s + 1);getchar();
    	int n = strlen(s + 1);s[++n] = '#';
    	vector<int> lists;
    	for(int i = 1;i <= n;++i)
    	{
    		if(s[i] == '#')	continue;
    		if(s[i] == '1')
    		{
    			int j = i,cur = 0;
    			while(s[j] == '1')
    			{
    				s[j] = '#';
    				++j;
    				++cur;
    			}
    			lists.push_back(cur);
    		}
    	}
    	sort(lists.begin(),lists.end());reverse(lists.begin(),lists.end());
    	int res = 0;
    	for(int i = 0;i < lists.size();++i)
    	{
    		if(i % 2 == 0)	
    			res += lists[i];
    	}
    	printf("%d
",res);
    }
    return 0;
}

C. Good Subarrays

题目大意:给定一个(n)个元素的序列a,求满足(sumlimits_{i=l}^r a_i = r - l + 1)的区间个数.

思路

先把求和拆成前缀和,得到(s_r - s_{l-1} = r - l + 1).固定区间右端点(r)找满足条件的(l)即可.注意(l)可以与(r)相等,因此(map)维护的时候要首先把(r)的影响加进去再计算,而不是先算再加入.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+7;
#define int ll
char a[N];
int s[N];
signed main()
{
    int T;scanf("%lld",&T);
    while(T--)
    {
    	int n;scanf("%lld",&n);
    	scanf("%s",a + 1);getchar();
    	ll res = 0;
    	map<int,int> tb;
    	for(int i = 1;i <= n;++i)
    	{
    		++tb[s[i - 1] - i + 1];
    		// cout << s[i - 1] - i + 1 << " ";
    		s[i] = s[i - 1] + (a[i] - '0');
    		res += tb[s[i] - i];
    		// cout << s[i] - i << endl;
    	}
    	printf("%lld
",res);
    }
    return 0;
}

D. Colored Rectangles

题目大意:给定若干个红色,绿色,蓝色的一对长度一样的棍子.问用这些棍子组成的颜色不同的矩形的面积的最大总和是多少.注意不能把两个相同颜色的一对棍子拆成两个分别去用.其次颜色不同指的是在两个集合里选的两对棍子.

数据范围:

(1 leq R,G,B leq 200)

思路

首先比较容易想到的是贪心,但是这个题会跟棍子的数目有关,贪心会有很多问题,而且打补丁的方式是解决不了的.

考虑(O(n^3))的DP,由于一共就三种元素,不妨就直接按定义直接设计状态:

状态:(f[i,j,k])表示红色用了(i)个,绿色用了(j)个,蓝色用了(k)个的前提下得到的矩形面积总和的最大值.

入口:全为0即可,不需要额外处理.

转移:三种匹配方式直接枚举即可.

出口:所有值的最大值.

没什么好说的,转移方程太过于简单了.注意防范可能的爆int.

不过今天群友聊天的时候特别提了一下这个D题.我一开始想的贪心,实际上有个结论是和DP做法联系很紧密的,就是对于两对选择比如:(a,b)(c,d)各是两种颜色,在(a leq b)(c leq d)的情况下,假如说两者不是较小的和较小的匹配,而是较小的和较大匹配,也就是错开的话,会发现得到的结果不会更好.进而推广可以得到:所有的选择一定是极值与极值的选择,不能错开选择.这就是这个DP的原理,当然估计好多人就理所当然的猜过去了.

当然这里的状态并不是直接表示说选哪个哪个,而是指在有多少个棍子给你的情况下,能拿到多少的值,对于当前的这个状态来说,他的前一个状态变过来,要加入两个新的最值,这就是前面那个结论的用处,这样的两个最值加过来只能是这两个最值自己匹配.所以说这里其实没有一个匹配谁的问题,整个DP过程暗含了这个事.其次的话,这个题其实排序规则是无所谓的,换成升序也可以过.

代码

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

const int N = 205;
int r[N],g[N],b[N];
int f[N][N][N];
signed main()
{
	ios::sync_with_stdio(0);cin.tie(0);
	int R,G,B;cin >> R >> G >> B;
	for(int i = 1;i <= R;++i)	cin >> r[i];sort(r + 1,r + R + 1,greater<int>());
	for(int i = 1;i <= G;++i)	cin >> g[i];sort(g + 1,g + G + 1,greater<int>());
	for(int i = 1;i <= B;++i)	cin >> b[i];sort(b + 1,b + B + 1,greater<int>());
	int res = 0;
	for(int i = 0;i <= R;++i)
	{
		for(int j = 0;j <= G;++j)
		{
			for(int k = 0;k <= B;++k)
			{
				auto& v = f[i][j][k];
				if(i >= 1 && j >= 1)	v = max(v,f[i - 1][j - 1][k] + r[i] * g[j]);
				if(j >= 1 && k >= 1)	v = max(v,f[i][j - 1][k - 1] + g[j] * b[k]);
				if(i >= 1 && k >= 1)	v = max(v,f[i - 1][j][k - 1] + r[i] * b[k]);
				res = max(res,v);
			}
		}
	}
	cout << res << endl;
    return 0;
}

E. Two Types of Spells

题目大意:有两种卡,一种是造成(x)点伤害,一种是造成(y)点伤害,并且让下一次的伤害翻倍.现给出(n)个操作,每个操作是学习到一个技能或者忘记一个技能,对每次修改输出用当前手上的卡最多能打出多少点的伤害.卡一回合只能用一次,只要不忘记下一轮还能用.

数据范围:

(1 leq n leq 2 * 10^5)

(-10^9 leq d_i leq 10^9)

数据保证你不会有相同伤害的卡.并且每个删除操作是合法的.

思路

先肯定模拟一下样例,就可以发现这个叠伤害的过程一个跟最小伤害的翻倍卡有关,因为整个过程总有一个翻倍卡不能翻倍,那既然这样肯定就让那个最小的去开始翻倍秀.其次,假如翻倍有(t)次的话,那么应该还要找前(t)大的值的和.

接下来尝试具体想一下整个过程:

  • 如果新加入一个卡

    此时对于一个新的卡来说,把他的权值记作是(d).那么对于新加入的一张卡来说,先不管他是不是翻倍的,首先可以把他加入前(t)个最大值的集合里去,那么这一步需要搞一个按(rank)查数的工具,显然要写一个平衡树了,假设已经实现了一个平衡树,继续往下推,这一步的具体操作就是先查一下(d)在里面可以排多少(rank),假如(rank leq t)那么就可以替换掉(t)了,直接一加一减维护就可以了.那么如果加入了一张翻倍卡会怎样呢?会发现如果加了一张翻倍的,(t)也会增加一,那么就再加入一个,也就是先把(t+1),完了之后再插一次(d).

  • 如果删除一张卡

    同样的,先不看这个卡具体是不是翻倍卡,如果这个卡的(rank)是在(t)以内的,那么我就是要把(cnt+1)的值给拿过来,再把这个值删掉.同样的像上面一样查找就好了.其次如果他确实是一张翻倍卡,那么就也一样的去查(cnt+1).再把值重复更新一下.

那么具体应该怎么搞前(t)大的元素就推完了.知道了这个信息之后,可以顺带的维护一下整个序列的所有的和(s)以及前(t)个最大的和(s_1),这个很好做就顺便加减一下就好了.那么接着往下想怎么算答案:假设当前所有的翻倍卡的最小值是(x)的话,

  • 如果(x)的排名是在(t)以内的,也就是(rank leq t)的话,显然他自己是不能作为翻倍的加入进去的,必须把它抠出来,也就是说,现在翻倍的和,实际上是前(t+1)个数的和减掉(x),因此想到这里,我们还要维护一个前(t+1)最大值的和(S_2).这个过程仿照上面同样维护就可以了.答案就是翻倍的部分加没翻倍的,实际上也就是总和加上要翻倍的部分,也就是(s + s_2 - x).
  • 反之,也就是不在(t)以内,这个时候(x)就不用纳入考虑了,直接让前面的翻倍再加上去就好了,答案就是(s_1+s).

之后对于怎么动态的查找最小值(x).显然加一个(set)就够了.

细节问题参考代码吧.

最后提一下:这个题的查找是按最大值排的排名,但是我的板子是最小的排名,我直接拉了个板子发现不对劲又自己打了半天fhq,发现好多内容都忘得差不多了...看来有时候还是要复习一下板子的内容,突然一下都不会写了.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
const int M = 1e6 + 7 + 2e5;
int n,idx,m;
int root = 0;
struct node
{
	int l,r;
	int val,key;
	int size;
}tree[M];
void update(int u)
{
	tree[u].size = tree[tree[u].l].size + tree[tree[u].r].size + 1;
}
void split(int u,int x,int &l,int &r)
{
	if(!u)
	{ 
		l = r = 0;
		return ;
	}
	if(tree[u].val >= x)
	{
		l = u;
		split(tree[u].r,x,tree[u].r,r);
	}
	else
	{
		r = u;
		split(tree[u].l,x,l,tree[u].l);
	}
	update(u);
}
void getnode(int x)
{
	++idx;
	tree[idx].size = 1;
	tree[idx].l = tree[idx].r = 0;
	tree[idx].val = x;tree[idx].key = rand();
}
int merge(int l,int r)
{
	if(!l || !r)	return l + r;
	if(tree[l].key < tree[r].key)
	{ 
		tree[l].r = merge(tree[l].r,r); 
		update(l);return l;
	}
	else
	{
		tree[r].l = merge(l,tree[r].l);
		update(r);return r;
	}
}
int kth(int u,int k)
{	
	if(k <= tree[tree[u].l].size)	return kth(tree[u].l,k);
	else if(k == tree[tree[u].l].size + 1)	return u;
	else
	{
		k -= tree[tree[u].l].size + 1;
		return kth(tree[u].r,k);
	}
}
int find_rank_by_value(int v)
{
	int l,r;
	split(root,v + 1,l,r);
	int res = tree[l].size + 1;
	root = merge(l,r);
	return res;
}
void insert(int x)
{
	int l,r;
	split(root,x,l,r);
	getnode(x);
	root = merge(merge(l,idx),r);
}
void remove(int x)
{
	int l,r,p;
	split(root,x,l,r);
	split(l,x + 1,l,p);
	p = merge(tree[p].l,tree[p].r);
	root = merge(merge(l,p),r);
}
int find_value_by_rank(int x)
{
	return tree[kth(root,x)].val;
}
signed main()
{
	scanf("%lld",&n);
	
	int cnt = 0,s1 = 0,s2 = 0,s = 0,all = 0;
	set<int> st;
	for(int i = 1; i <= n;++i)
	{
		int type,d;scanf("%lld%lld",&type,&d);
		s += d;
		int real = abs(d);
		int rk = find_rank_by_value(real);
		int res = 0;
		if(d > 0)
		{
			if(rk <= cnt)		s1 += real - find_value_by_rank(cnt);
			if(rk <= cnt + 1)	s2 += real - find_value_by_rank(cnt + 1);
			insert(real);
			if(type == 1)
			{
				++cnt;
				s1 += find_value_by_rank(cnt);
				s2 += find_value_by_rank(cnt + 1);
				st.insert(d);
			}
		}
		else
		{
			if(rk <= cnt)		s1 -= real - find_value_by_rank(cnt + 1);
			if(rk <= cnt + 1)	s2 -= real - find_value_by_rank(cnt + 2);
			remove(real);
			if(type == 1)
			{
				s1 -= find_value_by_rank(cnt);
				s2 -= find_value_by_rank(cnt + 1);
				--cnt;
				st.erase(real);
			}
		}
		if(cnt && find_rank_by_value(*(st.begin())) <= cnt)	
			res = s + s2 - *(st.begin());
		else res = s + s1;
		printf("%lld
",res);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/HotPants/p/13507239.html