XCPC真题(1):Bits Reverse | Empty Squares | Wall Painting

发布时间 2023-05-05 18:01:11作者: Eriktse0

? 作者:Eriktse
? 简介:19岁,211计算机在读,现役ACM银牌选手?力争以通俗易懂的方式讲解算法!❤️欢迎关注我,一起交流C++/Python算法。(优质好文持续更新中……)?
? 阅读原文获得更好阅读体验:https://www.eriktse.com/algorithm/1147.html

A. Bits Reverse

题目链接:https://codeforces.com/gym/102823/problem/D

题意

给定两个数字xy,现在有一种操作:将y的二进制数的连续三位进行翻转。问最少操作多少次可以使得x = y,或输出-1表示无法使得x = y

分析

因为每次固定选连续三位进行翻转,我们可以想一下连续三位翻转的一些性质。

首先中间这个数字肯定是不变的,然后两边的数字swap一下。

也就是每次会选择距离为2的两个数字进行一次交换,那么如果某个1在奇数位上,交换后依然在奇数位上,如果在偶数位上,交换后依然在偶数位上。

如下图,红色区域的二进制数和蓝色区域的二进制数是相互独立的。

所以我们可以进行奇偶分类的讨论,求出在奇数位上所需的操作次数,然后再求在偶数位上所需的操作次数即可。

我们可以发现,如果要使得操作后x = y,那么对于奇数位(即上图中的蓝色区域),y最高位的1一定对应x最高位的1,以此类推。而在奇数位上移动一格需要的操作次数是1次。

具体看下图的对应关系:

代码

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

const int N = 105;

int a[N], b[N], pa, pb;
int kase;

int getabs(int x){return x < 0 ? -x : x;}

void solve()
{
	cout << "Case " << ++ kase << ": ";
	int x, y;cin >> x >> y;
	pa = pb = 0;
	int ans = 0;
	for(int i = 0;i < 64; i += 2)
	{
		if(x >> i & 1)a[++ pa] = i;
		if(y >> i & 1)b[++ pb] = i;
	}
	if(pa != pb)
	{
		cout << -1 << '\n';
		return;
	}
	
	for(int i = 1;i <= pa; ++ i)ans += getabs(a[i] - b[i]) / 2;
	pa = pb = 0;
	for(int i = 1;i < 64; i += 2)
	{
		if(x >> i & 1)a[++ pa] = i;
		if(y >> i & 1)b[++ pb] = i;
	}
	
	if(pa != pb)
	{
		cout << -1 << '\n';
		return;
	}
	
	for(int i = 1;i <= pa; ++ i)ans += getabs(a[i] - b[i]) / 2;
	cout << ans << '\n';
}


signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int _;cin >> _;
	while(_ --)solve();
	return 0;	
}

B. Empty Squares

题目链接:https://codeforces.com/gym/104252/problem/E

题意

给定一个长度为n,高度为1的矩形,现在你手上有高度均为1,长度分别为1, 2, 3, ..., n的共n个矩形,但你一开始已经放了一个长度为k的矩形到中间,并使得左边空了e个格子。

问接下来你该怎么不重叠地放置矩形,可以使得剩余的空格最少,输出最少空格个数。

分析

看一眼数据范围,发现n只有1000,我们不放枚举最终矩形的形态(即左边有i个格子,中间k个格子,右边e个格子),然后判断一下这种情况能不能填满。

现在问题就变为了写一个函数f(l,k,r)求这种形态的矩形是否能够被恰好不留空位地构造出来。

进行一个简单的分类讨论(不妨设l <= r):

限制条件 情况
l != r != k 一定可以,就是用长度分别为l,r,k的矩形填充即可。
l = r = k 当k >= 5时一定可以构造出来(1+4+5+2+3),其余情况可以简单手算一下发现一定不行。
r=k 两个较大的相等,我们必须将r拆开,但是r可能拆出l,我们只需要枚举1 + (r - 1)2 + (r - 2)两种情况即可,l至多等于这四个数字当中的一个,所以两种情况必然有一种成立(注意保证i != r - i,拆出来的两个数字也不能相等)。
l=k或l=r 类似的拆开l即可。

ok了。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3 + 9;

bool f(int l, int k, int r)
{
	if(l != r && l != k && r != k)return true;
	if(l > r)swap(l, r);
	//l <= r
	if(l == k && k == r)
	{
		return l > 4;
	}else if(l == r || l == k)
	{
		for(int i = 1;i <= 2 && i < l - i; ++ i)
		{
			if(i != k && l - i != k)return true;
		}
	}else if(k == r)
	{
		for(int i = 1;i <= 2 && i < r - i; ++ i)
		{
			if(i != l && r - i != l)return true;
		}
	}
	return false;
}


signed main()
{
	int n, k, e;cin >> n >> k >> e;
	int ans = n - k;
	for(int i = 0;i <= e; ++ i)
	{
		for(int j = 0;j <= n - k - e; ++ j)
		{
			if(f(i, k, j))
			{
				//cout << "l, k, r = " << i << ' ' <<	k << ' ' << j << '\n';
				ans = min(ans, n - i - j - k);
			}
		}
	}
	cout << ans << '\n';
	return 0;
}

C. Wall Painting

题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=4810

题意

给定n个数字(题目好像没说清楚,应该是1e9以内的非负整数),求其中选出i个数字的所有组合的异或和之和。

分析

拆位,假设当前考虑的是第w位,那么我们只需要枚举出w位的所有组合情况并求和即可,不同位之互不影响(因为我们只需要所有的组合异或和之和)。

最外层枚举从n个数字中选出的数字的个数,即i,然后对于第w位,得到这一位上的cnt0cnt1分别表示二进制位上的0/1的个数(可以预处理出来)。

然后开始枚举选j1,自然就选i-j0,且这种情况的贡献为:

\[C_{cnt1}^{j} \times C_{cnt0}^{i-j} \times 2^w \times [j \% 2 = 1] \]

还有一种dp的写法,我自己写的时候是用dp的,比较sb,这就不写了。

代码

//C - Wall Painting
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1009, p = 1e6 + 3;
int C[N][N];
void init(int n)
{
	for(int i = 0;i <= n; ++ i)C[i][0] = 1;
	for(int i = 1;i <= n; ++ i)
		for(int j = 1;j <= n; ++ j)
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % p;
}

int n;
int a[N], c[50][2];


void solve()
{
	for(int i = 1;i <= n; ++ i)cin >> a[i];
	memset(c, 0, sizeof c);
	for(int i = 1;i <= n; ++ i)
		for(int j = 0;j <= 40; ++ j)
			c[j][a[i] >> j & 1] ++;
	
	
	//枚举选择的数字的个数i
	for(int i = 1;i <= n; ++ i)
	{
		int ans = 0;
		//枚举位数
		for(int w = 0;w <= 40; ++ w)
		{
			//在这一位上选择j个1和(i - j)个0
			for(int j = 1;j <= i && j <= c[w][1]; j += 2)
			{
				ans = (ans + (1ll << w) * C[c[w][1]][j] % p * C[c[w][0]][i - j] % p) % p;
			}
		}
		cout << ans << " \n"[i == n];
	}
		
}

signed main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	init(1002);
	while(cin >> n)solve();
		
	return 0;
}