Educational Codeforces Round 93 Div2 A~E题解

闲话

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

A. Bad Triangle

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

思路

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

代码

#include 
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",n);
    }
    return 0;
}

B. Substring Removal Game

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

思路

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

代码

#include 
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 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\n",res);
    }
    return 0;
}

C. Good Subarrays

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

思路

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

代码

#include 
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 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\n",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 
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());
	for(int i = 1;i <= G;++i)	cin >> g[i];sort(g + 1,g + G + 1,greater());
	for(int i = 1;i <= B;++i)	cin >> b[i];sort(b + 1,b + B + 1,greater());
	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 
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 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\n",res);
	}
	return 0;
}

你可能感兴趣的:(Educational Codeforces Round 93 Div2 A~E题解)