Codeforces Round #664 (Div. 2) 1395 A~D 题解

本场链接:Codeforces Round #664

闲话

我好菜啊.这场据说Pre写的不好,导致大片的fst.

A. Boboniu Likes to Color Balls

题目大意:有若干个r g b w的字符.你可以把一组r g b变成一个w,旧的三个字符就消失了.问是否可以拿这些字符在若干次操作后得到一个回文字符串.
数据范围:
\(0 \leq r,b,g,w \leq 10^9\)

思路

显然当一组字符可以组成一个回文串的时候,里面最多有1个字符的数量是奇数的.因为最多也就让奇数的那一组当中心.而对于题目所给的操作来说,最多不过4种可能性,就是在4步操作之类必然走过了所有的可能的结果,直接一个一个验证即可.

代码

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

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
		int a,b,c,d;scanf("%d%d%d%d",&a,&b,&c,&d);
		int ok = 0,cnt = 4;
		while(a >= 0 && b >= 0 && c >= 0 && cnt--)
		{
			int cnt = 0;
			cnt = a % 2 + b % 2 + c % 2 + d % 2;
			if(cnt <= 1)
			{
				ok = 1;
				printf("Yes\n");
				break;
			}
			--a;--b;--c;++d;
		}
		if(!ok)	puts("No");
    }
    return 0;
}

B. Boboniu Plays Chess

题目大意:给定一个\(n*m\)的棋盘以及一个初始位置.在场上的棋子相当于象棋里的车,可以横向或者纵向的移动若干个格子.问在不经过重复格子的前提下是否能遍历完所有格子.输出一个可行的方案

数据范围:
\(1 \leq n,m \leq 100\)

思路

比较明显,就单纯的搞一个方案出来的话,直接先把棋子拉到左上角再一步一步往旁边走,每次走的时候检测一下是否是最开始走过的三个位置即可.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define x first
#define y second
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);
	int n,m,x,y;cin >> n >> m >> x >> y;
	vector<pii> op;
	pii _1 = {x,y},_2 = {x,1},_3 = {1,1};
	op.push_back(_1),op.push_back(_2),op.push_back(_3);
	for(int i = 1;i <= m;++i)
	{
		int y;
		if(i % 2)	y = 1;
		else y = n;
		while(1)
		{
			pii cur = {y,i};
			if(cur != _1 && cur != _2 && cur != _3)
				op.push_back(cur);
			if(i % 2)	++y;
			else --y;
			if(y > n || y < 1)	break;
		}
	}
	for(auto& v : op)	cout << v.x << " " << v.y << endl;
    return 0;
}

C. Boboniu and Bit Operations

原题大意:给定一个\(n\)长度序列a,以及一个\(m\)长度的序列b.构造一个新的序列c,其中\(c_i = a_i \& b_j\),\(b_j\)是在序列里任选的一个元素,并且可以任意多次的使用.现要使序列c的或和最小,即\(c_1 | c_2 | c_3 | ... c_n\)最小,求出答案,不需要方案.

数据范围:
\(1 \leq n,m \leq 200\)
\(0 \leq a_i \leq 2^9\)
\(0 \leq b_i \leq 2^9\)

思路

这个形式看起来很诡异,首先尝试一下化简,但是会发现没有任何思路可以拿出来把这个式子变成一个可以直接考虑的.注意到数据范围是非常小的,不仅是\(n,m\)很小,连序列里的数字都很小.考虑暴力:对于答案来说,他是整个序列\(c\)的或和,而每个\(c_i\)都是原来序列里的两个与的结果,由于位运算的特点就是不进位,所以答案\(x\)必然有一个可以确定的范围,即\(x \in [0,2^9-1]\).换句话说,如果能找到某种方式验证当前的答案是否是可以构造出来的,那么从小到大遍历所有答案的可能,必然可以获得最小的答案.

考虑怎么check一个答案x是合法的:由于x是若干个\(c_i\)或出来的,所以只要某一个\(c_i\)的第\(k\)位是1,那么最终答案\(x\)的第\(k\)位也必然会是1.进而可以想到:由于或的特殊性,导致如果当前的这个答案是可以构造出来的,那么对于这个答案的每一位\(0\)来说,必须要求所有的\(c_i\)都不能存在这一位是\(1\),进而对于每一个\(c_i\)来说都有:\(c_i | x = x\),因为不可能存在某一个位置是自己是1而答案是0,导致这个答案变大,或的结果也不可能变小.之后直接枚举每一个\(a_i\)看是否存在一个\(b_j\)使得\(a_i \& b_j | x = x\)即可,如果找完了都没有,说明当前的答案是构造不出来的.其次从小到大枚举可能的答案也可以确定第一个找到的就是最小的答案.

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 205;
int a[N],b[N];
int n,m;
bool check(int res)
{
	for(int i = 1;i <= n;++i)
	{
		int ok = 0;
		for(int j = 1;j <= m;++j)
		{
			if((a[i] & b[j] | res) == res)
			{
				ok = 1;
				break;
			}
		}
		if(!ok)	return 0;
	}
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;++i)	scanf("%d",&a[i]);
	for(int i = 1;i <= m;++i)	scanf("%d",&b[i]);
	
	for(int res = 0;res < (1 << 9);++res)
	{
		if(check(res))
		{
			printf("%d\n",res);
			return 0;
		}
	}

    return 0;
}

D. Boboniu Chats with Du

原题大意:有一个毒瘤在膜群主,群主的心情值\(m\)是一个定值.每天这个毒瘤会想到一个膜人方式,这个膜人方式对于毒瘤自己来说有\(a_i\)的愉悦值,但是对群主来说,如果\(a_i > m\)则群主会很不爽并禁言毒瘤\(d\)天(假设第\(i\)天被禁言了,那么\(i+1,i+2...min(i+d,n)\)是被禁言的状态).现在这个毒瘤已经一次性想好了\(n\)个膜群主的方式,可以在每一天任选一个没有用过的膜群主,问这个毒瘤在\(n\)天时最多能获得多少愉悦值.

数据范围:
\(1 \leq d \leq n \leq 10^5\)
\(0 \leq m \leq 10^9\)
\(0 \leq a_i \leq 10^9\)

思路

不妨把所有的方式分成两类,一种是会被禁言的,一种是不会被禁言的.在分完组之后,这个题的复杂度大概是\(O(nlogn)\)级别的,因此可能就是一个排序,或者是直接枚举.在这个思路下,可以想到:如果要被禁言,那么选大的肯定比选小的好,反正都要被禁言,不如选多点.其次,不妨把会被禁言的放在最后面,就是最后一天的放一个会被禁言的,这样就可以多考虑一些可能性了,反正只考虑到\(n\)天.而不禁言的就直接从开头堆,同样的也是按从大到小选,反正不会被禁言.
到这里可以想到第一个方向:先从大到小排个序让选择的尽量大,其次把会被禁言的往后丢,使最后一次放在最后一天.那排序肯定不是一直在排,剩下的过程可以直接枚举一共用了几个会被禁言的膜人技巧,如果可以以\(O(1)\)的方式算出整个结果或者带一个\(log\)都是可以接受的.
假设现在是枚举一共用了\(x\)个会被禁言的,那么根据之前说的,实际上一共有\((x-1)*(d+1)+1\)天被用掉了,首先一共用了\(x\)个,那么有\(x-1\)个空隙,而算上使用的那一天,每一段就是\(d+1\)天,最后还有一个额外的最后一天也得补上.这一部分是很好算的,每次枚举的时候顺便加一个最新的膜人技巧的愉悦值进去就可以了.其次剩下了\(n - (x-1)*(d+1)-1\)天可以放不会被禁言的,这部分显然可以直接搞一个前缀和就行了.

之后直接更新答案即可,不过要注意在算\((x-1)*(d+1)\)的时候可能会爆int.要注意,以及在算最后剩几个选择的时候也要注意前缀和的边界.

代码

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

int main()
{
	int n,d,m;scanf("%d%d%d",&n,&d,&m);
	vector<int> u,l;
	for(int i = 1;i <= n;++i)
	{
		int x;scanf("%d",&x);
		if(x > m)	u.push_back(x);
		else l.push_back(x);
	}
	if(u.size() == 0)
	{
		ll res = 0;
		for(int i = 0;i < n;++i)	res += l[i];
		printf("%lld\n",res);
		return 0;
	}
	sort(u.begin(),u.end());sort(l.begin(),l.end());
	reverse(u.begin(),u.end());reverse(l.begin(),l.end());
	vector<ll> lsum;lsum.push_back(0ll);
	for(int i = 0;i < l.size();++i)	lsum.push_back(lsum.back() + l[i]);
	ll res = 0,cur = 0;
	for(int x = 1;x <= u.size();++x)
	{
		// if(n < (x - 1) * (d + 1) + 1)	break;
		cur += u[x - 1];
		ll last = ll(x - 1) * (d + 1) + 1;
		if(last > n)	continue;
		last = n - last;
		// cout << cur << " " << last << " " << lsum[last] << endl;
		res = max(res,cur + (last >= lsum.size() ? lsum.back() : lsum[last]));		
	}
	printf("%lld",res);
    return 0;
}
posted @ 2020-08-13 15:45  随处可见的阿宅  阅读(178)  评论(0编辑  收藏  举报