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;
}