• 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;
    }
    
  • 相关阅读:
    896. Monotonic Array单调数组
    865. Smallest Subtree with all the Deepest Nodes 有最深节点的最小子树
    489. Robot Room Cleaner扫地机器人
    JavaFX
    《Python CookBook2》 第一章 文本
    《Python CookBook2》 第一章 文本
    《Python CookBook2》 第一章 文本
    《Python CookBook2》 第一章 文本
    《Python CookBook2》 第一章 文本
    《Python CookBook2》 第一章 文本
  • 原文地址:https://www.cnblogs.com/HotPants/p/13507239.html
Copyright © 2020-2023  润新知