2022.2.18解题报告

2022.2.18解题报告

T1.切蛋糕

题目描述:

在这里插入图片描述

思路:

首先,我们先来看一下最少用几刀就可以解决所有情况。
对于一个蛋糕,要分成至少三块,那么最少都要2刀,因为0刀或1刀分出的蛋糕数量都小于3。
那么,两刀能不能做到呢?我们来看下面这张图:
在这里插入图片描述
由于一共只有4块蛋糕而且要分给3个人,所以一定是2个人拿到1块,1个人拿到2块。
不妨设a和b拿到了1块,且a拿走了上图中A部分,则b只有B和C处两种选择。
①如果b拿走了B部分,那么就有a = b的结论,这显然只是一种片面的情况,无法解决所有情况。
②如果b拿走了C部分,那么a和b拿走的部分刚好组成一个半圆,也就是说能得出a + b = c的结论,这也是一种片面的情况。
因此,只切两刀是无法解决所有情况的。
现在来看切三刀的情况:
在这里插入图片描述
如图,假设在左半圆中,a,b,c分别取走了A,B,C部分,那么我们只需要做到A : B : C = a : b : c即可。这是可以解决任意情况的。

那么现在,我们只需要把那些可以用更少次数解决的特殊情况找出来就可以了。
我们按照a,b,c中为0的数字的个数来分类:
①2个数字为0。不妨设是a和b为0,那么只需要0刀,即把整个蛋糕都给c就可以了。
②1一个数字为0。不妨设是a为0,那么只需要将整个蛋糕分给b和c两个人即可。此处又有两种情况。
(1)若b与c相等,那么只需要1刀,将整个蛋糕均分为两半即可。
(2)若b与c不相等,那么如下图,假设b和c分别拿走了B和C部分,只需要在左半圆中满足B : C = b : c即可,这需要2刀。
在这里插入图片描述
③若3个数字都不为0,这里有两种特殊情况,就是最开始讨论得出的两种:
(1)若a = b,则只需要2刀。
(2)若a + b = c,则只需要2刀。
综上所述,我们只需要先判断一下是否是特殊情况,如果是,就按照对应的情况刀数输出;否则就输出3刀。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
LL a[4];
int T;

int main() {
	scanf("%d", &T);
	while (T -- ) {
		scanf("%d%d%d", &a[1], &a[2], &a[3]);
		sort(a + 1, a + 4);
		if (!a[1]) { //存在等于0的数
			if (!a[2]) {
				puts("0");
				continue;
			} else if (a[2] == a[3]) {
				puts("1");
				continue;
			} else {
				puts("2");
				continue;
			}
		}
		if (a[1] == a[2] || a[2] == a[3] || a[1] == a[3]) {
			puts("2");
			continue;
		} else if (a[1] + a[2] == a[3]) {
			puts("2");
			continue;
		} else {
			puts("3");
			continue;
		}
	}
	return 0;
}

T2.吃豆人

题目描述:

在这里插入图片描述

思路:

这道题目中的行动方式看似复杂,实际上很简单,如图:
在这里插入图片描述
可以看出,在点阵中,吃豆人的行动方式使得它只会走环形或走对角线。因此,我们就能够把题目简化为从所有环和对角线中选出两个,使得经过的点和最大。
这样一来,我们面前就只剩两个问题了:
①环上点和的预处理
②两个环之间重叠部分的处理

先看第一个问题:
我们可以用偏移量数组来解决遍历环上的点求和的问题。两个数字分别为:
dx[4] = {1, 1, -1, -1}
dy[4] = {-1, 1, 1, -1}
它们分别表示往左下、右下、右上、左上四个方向走一步。
这样一来,我们就可以利用这两个数组,先沿着一个方向走,碰到墙时就换一个方向。
那么一共要走几步,即遍历几个点呢?
注意到对于每一个环,除了最上面和最下面两个顶点,每一行都有两个点。所以一共会有2 * (n - 2) + 2 = 2 * (n - 1)个点。

现在再来看第二个问题:
一般来说,两个环之间会有4个重复的点,我们要在计算时减去它们一次。那么怎么找到这些点的左边呢?
首先,我们可以先得出最上面的那个交点的坐标,如图:
在这里插入图片描述
它的坐标是多少呢?可以看出,i和j每向下一行,就会靠近2单位长度,即二者间的距离-2。因此,交点的行数为(j - i) / 2,列数为i + (j - i) / 2
即交点的坐标为((j - i) / 2, i + (j - i) / 2)。
这样一来,左下角的那个交点是和该交点关于左上-右下的对角线对称的,所以它的坐标为(i + (j - i) / 2, (j - i) / 2)。
那么,怎么求右边的那两个交点的坐标呢?
如图:
在这里插入图片描述
图中右边那个交点由于是关于右上-左下这条对角线和最上面的交点对称的,所以图中蓝色线段和橙色线段是相等的。那么它的坐标就一目了然了,是(n - 1 - (i + (j - i) / 2), n - 1 - (j - i) / 2)。
根据对称性,最后一个交点的坐标也就能求出来了,自此,四个交点的坐标全部求出来了,只需要把它们一一减去即可求出点和。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <set>

#define x first
#define y second

using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int g[N][N], n;
int s[N];

int dx[4] = {1, 1, -1, -1};

int dy[4] = {-1, 1, 1, -1};

int main() {
	;
	scanf("%d", &n);
	for (int i = 0 ; i < n ; i ++ )
		for (int j = 0 ; j < n ; j ++ )
			scanf("%d", &g[i][j]);

	for (int i = 0 ; i < n ; i ++ ) { //先计算两条对角线上的点和
		s[0] += g[i][i];
		s[n - 1] += g[i][n - 1 - i];
	}

	for (int i = 1 ; i < n - 1 ; i ++ ) { //预处理所有环上的点和
		int x = 0, y = i, d = 0;
		for (int j = 0 ; j < (n - 1) * 2 ; j ++ ) {
			s[i] += g[x][y];
			int a = x + dx[d], b = y + dy[d];
			if (a < 0 || a > n - 1 || b < 0 || b > n - 1) {
				d = (d + 1) % 4;
				a = x + dx[d], b = y + dy[d];
			}
			x = a, y = b;
		}
	}

	int res = 0;
	for (int i = 0 ; i < n ; i ++ ) {
		for (int j = i + 1 ; j < n ; j ++ ) {
			if ((j - i) % 2) //如果两个环的最顶点之间的距离为奇数,那么它们不会有交点
				res = max(res, s[i] + s[j]);
			else {
				int sum = s[i] + s[j];
				set<PII> S;
				int a = (j - i) / 2, b = i + a; //左边的两个交点坐标
				S.insert({a, b}), S.insert({b, a});
				int x = n - 1 - b, y = n - 1 - a; //右边的两个交点坐标
				S.insert({x, y}), S.insert({y, x});
				for (auto &p : S)
					sum -= g[p.x][p.y];
				res = max(res, sum);
			}
		}
	}
	printf("%d\n", res);
	return 0;
}

T3.重力球

题目描述:

在这里插入图片描述

思路:

首先,我们来看一下在每一次操作过后两个小球的状态所满足的性质:
①两个小球都被障碍物卡住
②两个小球被卡在同一方向
那么,我们就可以定义一个状态:(x, y, d)
其中x,y表示两个小球分别被x号和y号障碍物卡住;d表示小球被卡在障碍物的哪一个方向。
这是方向的定义:
在这里插入图片描述
我们的目的是预处理出每一种状态到终点(即两小球重合的状态)的最短路径,对此我们可以将终点看作起点,反向作边,来求一遍多源最短路。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>

#define x first
#define y second

using namespace std;
typedef pair<int, int> PII;
const int N = 255, M = 4e6 + 10, INF = 1e8;

int n, m, Q;

int id[N][N]; //存储每个障碍物的编号
int cnt;
PII ps[N * 5];
bool g[N][N]; //存储哪些格子中有障碍物





int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //方向数组
int tr[N][N][4]; //tr[i][j][k]表示对于点(i, j)来说,它在k方向上的第一个障碍物的编号

struct Node { //队列
	int x, y, d;
} q[M / 4], e[M];
int h[N * 5][N * 5][4], ne[M], idx; //邻接表
int dist[N * 5][N * 5][4];

bool check(int x, int y) { //判断一个点(x, y)是不是空格
	return x >= 1 && x <= n && y >= 1 && y <= n && !g[x][y];
}

void build() {
	for (int i = 0 ; i <= n + 1 ; i ++ ) //左方向
		for (int j = 0, last ; j <= n + 1 ; j ++ )
			if (g[i][j])
				last = id[i][j];
			else
				tr[i][j][3] = last;

	for (int i = 0 ; i <= n + 1 ; i ++ ) //右方向
		for (int j = n + 1, last ; j >= 0 ; j -- )
			if (g[i][j])
				last = id[i][j];
			else
				tr[i][j][1] = last;

	for (int i = 0 ; i <= n + 1 ; i ++ ) //上方向
		for (int j = 0, last ; j <= n + 1 ; j ++ )
			if (g[j][i])
				last = id[j][i];
			else
				tr[j][i][0] = last;

	for (int i = 0 ; i <= n + 1 ; i ++ ) //下方向
		for (int j = n + 1, last ; j >= 0 ; j -- )
			if (g[j][i])
				last = id[j][i];
			else
				tr[j][i][2] = last;

	memset(h, -1, sizeof h);
	for (int i = 1 ; i <= cnt ; i ++ ) //枚举一下每种状态,两个点的编号分别为i和j
		for (int j = 1 ; j <= cnt ; j ++ )
			for (int k = 0 ; k < 4 ; k ++ ) { //枚举一下走向每一种方向的情况
				int x1 = ps[i].x + dx[k], y1 = ps[i].y + dy[k];
				int x2 = ps[j].x + dx[k], y2 = ps[j].y + dy[k];
				if (check(x1, y1) && check(x2, y2)) { //如果两个点都是空格
					for (int u = 0 ; u < 4 ; u ++ ) { //就枚举一下每一种方向
						auto &t = h[tr[x1][y1][u]][tr[x2][y2][u]][u ^ 2];
						e[idx] = {i, j, k}, ne[idx] = t, t = idx ++ ; //建图
					}
				}
			}
}

void bfs() {
	int hh = 0, tt = -1; //定义队列
	memset(dist, 0x3f, sizeof dist);
	for (int i = 1 ; i <= cnt ; i ++ )
		for (int j = 0 ; j < 4 ; j ++ ) { //先检查一下当前格子是不是空格
			int x = ps[i].x + dx[j], y = ps[i].y + dy[j];
			if (check(x, y)) { //如果是空格,就可以走
				dist[i][i][j] = 0; //这里到终点的距离为0
				q[ ++ tt] = {i, i, j}; //入队
			}
		}

	while (hh <= tt) {
		auto t = q[hh ++ ];
		for (int i = h[t.x][t.y][t.d] ; ~i ; i = ne[i]) {
			auto &r = e[i];
			if (dist[r.x][r.y][r.d] > dist[t.x][t.y][t.d] + 1) {
				dist[r.x][r.y][r.d] = dist[t.x][t.y][t.d] + 1;
				q[ ++ tt] = r;
			}
		}
	}
}

int main() {
	scanf("%d%d%d", &n, &m, &Q); //棋盘大小、障碍物总数、询问总数
	while (m -- ) {
		int x, y;
		scanf("%d%d", &x, &y);
		g[x][y] = true;
		id[x][y] =  ++ cnt;
		ps[cnt] = {x, y};
	}

	for (int i = 0 ; i <= n + 1 ; i ++ ) //将边界也算入障碍物内
		for (int j = 0 ; j <= n + 1 ; j ++ )
			if (!i || !j || i == n + 1 || j == n + 1) { //如果这个点在边界上
				id[i][j] =  ++ cnt;
				ps[cnt] = {i, j};
				g[i][j] = true;
			}

	build(); //预处理转移数组
	bfs();

	while (Q -- ) {
		int x1, x2, y1, y2;
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		int res = INF;
		for (int i = 0 ; i < 4; i ++ )
			res = min(res, dist[tr[x1][y1][i]][tr[x2][y2][i]][i ^ 2] + 1); //先让两个小球被卡住,再直接查表得出结果
		if (x1 == x2 && y1 == y2)
			res = 0;
		if (res == INF)
			res = -1;
		printf("%d\n", res);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值