zoukankan      html  css  js  c++  java
  • 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;
    }
    
  • 相关阅读:
    NOIp 图论算法专题总结 (3):网络流 & 二分图 简明讲义
    zkw 线段树
    NOIp 图论算法专题总结 (2)
    NOIp 数据结构专题总结 (2):分块、树状数组、线段树
    单调栈、单调队列 思路及例题
    java自动装箱和拆箱
    HashMap和HashTable的异同点
    HttpServletRequest. getParameter获取的参数格式
    关于交换函数(1)
    std::vector::iterator重载了下面哪些运算符
  • 原文地址:https://www.cnblogs.com/HotPants/p/13507239.html
Copyright © 2011-2022 走看看