题目大意

在一个 \(n\times n\) 的矩形中,题目会给出 \(m\) 个障碍物。有两个小球,你可以选定四个方向(上下左右)的其中一个,小球会朝着这四个方向一直滚动,直到遇到障碍物或是矩形的边缘停止。有 \(q\) 条形如 \(a\) \(b\) \(c\) \(d\) 的询问,代表两个小球的坐标 \((a,b)\) 和 \((c,d)\) ,求多少步,小球会重叠。

题目链接

思路

55pts

首先考虑暴力,先预处理出所有点滚动会滚动到哪里,写四个 \(dfs\) 完事。

int L(int i, int j) {//向左滚
if(stn[i][j - 1]) {
l[i][j] = L(i, j - 1);
return l[i][j];
}
else
l[i][j] = j;
return j;
}
int U(int i, int j) {//向上滚
if(stn[i - 1][j]) {
u[i][j] = U(i - 1, j);
return u[i][j];
}
else
u[i][j] = i;
return i;
}
int R(int i, int j) {//向右滚
if(stn[i][j + 1]) {
r[i][j] = R(i, j + 1);
return r[i][j];
}
else
r[i][j] = j;
return j;
}
int D(int i, int j) {//向下滚
if(stn[i + 1][j]) {
w[i][j] = D(i + 1, j);
return w[i][j];
}
else
w[i][j] = i;
return i;
}

主函数中:

for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(!stn[i][j])//小球不会到障碍物上
continue;
if(!r[i][j])
R(i, j);
if(!w[i][j])
D(i, j);
}
}
for(int i = n; i >= 1; i--) {
for(int j = n; j >= 1; j--) {
if(!stn[i][j])//小球不会到障碍物上
continue;
if(!l[i][j])
L(i, j);
if(!u[i][j])
U(i, j);
}
}

然后无脑 \(BFS\) ,\(TLE\) 。( \(55pts\) 亲测)

主体部分

void BFS() {
q.push(Node(a, b, c, d, 0));
if(a == c && b == d) {
printf("0\n");
return;
}
while(!q.empty()) {
Node now = q.front(); q.pop();
Node next = now;
next.step++;
next.Y_1 = l[next.X_1][next.Y_1];
next.Y_2 = l[next.X_2][next.Y_2];
if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
printf("%d\n", next.step);
return;
}
int tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
if(!f[tmp]) {
f[tmp] = 1;
q.push(next);
} next = now;
next.step++;
next.Y_1 = r[next.X_1][next.Y_1];
next.Y_2 = r[next.X_2][next.Y_2];
if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
printf("%d\n", next.step);
return;
}
tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
if(!f[tmp]) {
f[tmp] = 1;
q.push(next);
} next = now;
next.step++;
next.X_1 = u[next.X_1][next.Y_1];
next.X_2 = u[next.X_2][next.Y_2];
if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
printf("%d\n", next.step);
return;
}
tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
if(!f[tmp]) {
f[tmp] = 1;
q.push(next);
} next = now;
next.step++;
next.X_1 = w[next.X_1][next.Y_1];
next.X_2 = w[next.X_2][next.Y_2];
if(next.X_1 == next.X_2 && next.Y_1 == next.Y_2) {
printf("%d\n", next.step);
return;
}
tmp = Get_Hash(next.X_1, next.X_2, next.Y_1, next.Y_2);
if(!f[tmp]) {
f[tmp] = 1;
q.push(next);
}
}
printf("-1\n");
}

100pts

考虑逆推求出所有状态的最小满足条件步数。

不难发现,经过一次的滚动后,小球会落在障碍物的旁边或是矩阵的边缘。一共有 \((4m+4n)\) 中状态,那么两个求就一共有 \((4m+4n)^2\) 种状态,可以往四边滚,那么可以将会与其他的四种状态有联系。

在考虑将这些点进行 \(hash\) 相连。那么就成为了一个多源最短路问题。将一个超级源点 \(s\) 连向每个两小球重叠的哈希值连边。同时当前状态与下一个状态建立反边,跑最短路即可。

由于边长都为 \(1\) ,则可以使用 \(BFS\) 来求最短路,时间复杂度为 \(O(4(4m+4n)^2)\) 。

最后是查询的问题,也比较简单, 分四个方向先滚动一次,那么步数就是 \(dis[tmp]+1\) ( \(dis\) 记录最短路, \(tmp\) 为当前状态的哈希值)。但值得注意的是,若滚动前和滚动后两个小球的位置没有改变,则不需要加一。

细节代码上有注释。

Code

#include <queue>
#include <cstdio>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 2e3 + 5;
const int MAXM = 5e7 + 5;
struct Node {//小球的位置
int X_1, Y_1, X_2, Y_2;
Node() {}
Node(int A, int B, int C, int D) {
X_1 = A;
Y_1 = B;
X_2 = C;
Y_2 = D;
}
friend bool operator == (Node x, Node y) {
return (x.X_1 == y.X_1) && (x.X_2 == y.X_2) && (x.Y_1 == y.Y_1) && (x.Y_2 == y.Y_2);
}
};
struct Edge {//链式前向星存边,别用vector
int To, Next;
};
Edge edge[MAXM];
int head[MAXM];
int u[MAXN][MAXN], w[MAXN][MAXN], l[MAXN][MAXN], r[MAXN][MAXN];
bool stn[MAXN][MAXN], can[MAXN][MAXN], vis[MAXM];
int dis[MAXM], Hash[MAXN][MAXN];
pair<int, int> id[MAXN];
queue<int> q;
int x[MAXN], y[MAXN];
int n, m, s, Q;
int a, b, c, d;
int tot, cnt;
void Addedge(int u, int v) {//加入边
edge[++tot].Next = head[u];
edge[tot].To = v;
head[u] = tot;
}
int Get_Hash(int A, int B, int C, int D) {//哈希值
if(A > C || (A == C && B > D)) {//注意先排序,再hash
swap(A, C);
swap(B, D);
}
int x = Hash[A][B];
int y = Hash[C][D];
return x * 2001 + y;//共有(4n+4m)种状态,大概是2000,这样不会发生冲突
}
int L(int i, int j) {//向左走
if(stn[i][j - 1]) {
l[i][j] = L(i, j - 1);
return l[i][j];
}
else
l[i][j] = j;
return j;
}
int U(int i, int j) {//向上走
if(stn[i - 1][j]) {
u[i][j] = U(i - 1, j);
return u[i][j];
}
else
u[i][j] = i;
return i;
}
int R(int i, int j) {//向右走
if(stn[i][j + 1]) {
r[i][j] = R(i, j + 1);
return r[i][j];
}
else
r[i][j] = j;
return j;
}
int D(int i, int j) {//向下走
if(stn[i + 1][j]) {
w[i][j] = D(i + 1, j);
return w[i][j];
}
else
w[i][j] = i;
return i;
}
void Build() {//建图
int tmp;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(can[i][j] && stn[i][j]) {//是障碍物边缘且不失障碍物
id[++cnt].first = i;
id[cnt].second = j;
Hash[i][j] = cnt;
tmp = Get_Hash(i, j, i, j);
Addedge(s, tmp);//超级源点连结果
}
for(int i = 1; i <= cnt; i++) {
for(int j = i + 1; j <= cnt; j++) {//下一状态连边,注意是反向边
Node now = Node(id[i].first, id[i].second, id[j].first, id[j].second);
int to = Get_Hash(id[i].first, id[i].second, id[j].first, id[j].second);
Node next = now;
next.Y_1 = l[next.X_1][next.Y_1];
next.Y_2 = l[next.X_2][next.Y_2];
int tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
Addedge(tmp, to);
next = now;
next.Y_1 = r[next.X_1][next.Y_1];
next.Y_2 = r[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
Addedge(tmp, to);
next = now;
next.X_1 = u[next.X_1][next.Y_1];
next.X_2 = u[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
Addedge(tmp, to);
next = now;
next.X_1 = w[next.X_1][next.Y_1];
next.X_2 = w[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
Addedge(tmp, to);
}
}
}
void Shortestpast() {//多源最短路
q.push(s);
vis[s] = 1;
dis[s] = -1;
while(!q.empty()) {//边长为1用BFS
int now = q.front(); q.pop();
for(int i = head[now]; i; i = edge[i].Next) {
int next = edge[i].To;
if(!vis[next]) {
vis[next] = 1;
dis[next] = dis[now] + 1;
q.push(next);
}
}
}
}
int Query() {
if(a == c && b == d)//已经重叠不用滚
return 0;
Node now = Node(a, b, c, d);
int tmp, res = INF;
Node next = now;
next.Y_1 = l[next.X_1][next.Y_1];
next.Y_2 = l[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
if(vis[tmp]) {//如果最短路中被标记过才更新
if(next == now)
res = min(res, dis[tmp]);//还是在原位置,不加1
else
res = min(res, dis[tmp] + 1);
}
next = now;
next.Y_1 = r[next.X_1][next.Y_1];
next.Y_2 = r[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
if(vis[tmp]) {
if(next == now)
res = min(res, dis[tmp]);
else
res = min(res, dis[tmp] + 1);
}
next = now;
next.X_1 = u[next.X_1][next.Y_1];
next.X_2 = u[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
if(vis[tmp]) {
if(next == now)
res = min(res, dis[tmp]);
else
res = min(res, dis[tmp] + 1);
}
next = now;
next.X_1 = w[next.X_1][next.Y_1];
next.X_2 = w[next.X_2][next.Y_2];
tmp = Get_Hash(next.X_1, next.Y_1, next.X_2, next.Y_2);
if(vis[tmp]) {
if(next == now)
res = min(res, dis[tmp]);
else
res = min(res, dis[tmp] + 1);
}
if(res != INF)
return res;//找到答案
return -1;//没有答案
}
int main() {
scanf("%d %d %d", &n, &m, &Q);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
stn[i][j] = 1;
for(int i = 1; i <= m; i++) {
scanf("%d %d", &x[i], &y[i]);
stn[x[i]][y[i]] = 0;//障碍物标记
can[x[i]][y[i] + 1] = 1;//障碍物四周标记
can[x[i] + 1][y[i]] = 1;
can[x[i]][y[i] - 1] = 1;
can[x[i] - 1][y[i]] = 1;
}
for(int i = 1; i <= n; i++)
can[1][i] = can[n][i] = can[i][1] = can[i][n] = 1;//矩阵四周标记
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
if(!stn[i][j])
continue;
if(!r[i][j])
R(i, j);
if(!w[i][j])
D(i, j);
}
}
for(int i = n; i >= 1; i--) {
for(int j = n; j >= 1; j--) {
if(!stn[i][j])
continue;
if(!l[i][j])
L(i, j);
if(!u[i][j])
U(i, j);
}
}
Build();
Shortestpast();
for(int i = 1; i <= Q; i++) {
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("%d\n", Query());
}
return 0;//完结撒花
}

[题解] [NOI Online 2021 入门组 T3] 重力球的更多相关文章

  1. P7473 [NOI Online 2021 入门组] 重力球

    P7473 [NOI Online 2021 入门组] 重力球 题意 给你一个正方形平面,某些位置有障碍,对于平面上两个球,每次你可以改变重力方向使两个球下落到最底端,求使两个球位置重合的最小改变重力 ...

  2. NOI Online 2021 入门组 T1

    Description 题目描述 Alice.Bob 和 Cindy 三个好朋友得到了一个圆形蛋糕,他们打算分享这个蛋糕. 三个人的需求量分别为 \(a, b, c\),现在请你帮他们切蛋糕,规则如下 ...

  3. CCF NOI Online 2021 提高组 T3 岛屿探险(CDQ 分治,Trie 树)

    题面 凇睦是一个喜欢探险的女孩子,这天她到一片海域上来探险了. 在这片海域上一共有 n 座岛屿排成一排,标号为 1, 2, 3, . . . , n.每座岛屿有两个权值,分别为劳累度 ai 和有趣度 ...

  4. P6474 [NOI Online #2 入门组] 荆轲刺秦王

    P6474 [NOI Online #2 入门组] 荆轲刺秦王 bfs+差分+卡常 本来我其实是场内选手,但是因为记错提交时间,晚了半小时才交,交不上了,就自动降级为了场外选手 题面复杂,不简述了 首 ...

  5. [NOI Online 2021 提高组] 积木小赛

    思路不说了. 想起来自己打比赛的时候,没睡好.随便写了个\(HASH\),模数开小一半分都没有. 然后学了\(SAM\),发现这个判重不就是个水题. \(SAM\)是字串tire的集合体. 随便\(d ...

  6. 洛谷 P6189 - [NOI Online #1 入门组]跑步(根号分治+背包)

    题面传送门 题意: 求有多少个数列 \(x\) 满足: \(\sum x_i=n\) \(x_i\geq x_{i+1}\) 答案对 \(p\) 取模. ...你确定这叫"入门"组 ...

  7. [NOI 2020 Online] 入门组T1 文具采购(洛谷 P6188)题解

    原题传送门 题目部分:(来自于考试题面,经整理) [题目描述] 小明的班上共有 n 元班费,同学们准备使用班费集体购买 3 种物品: 1.圆规,每个 7 元. 2.笔,每支 4 元. 3.笔记本,每本 ...

  8. CCF NOI Online 2021 提高组 赛后心得

    T1 做个,不会,拿到 20 pts 跑路. 注意后面有个 K = 1 的部分分,这个可以递推求 b 的个数,然后直接乘上 a0 . 官方正解讲得极其详细,我还是第一次见到可以 O(K2) 做 1~n ...

  9. CCF NOI Online 2021 提高组 T2 积木小赛 (子序列自动机+后缀自动机,O(n^2))

    题面 Alice 和 Bob 最近热衷于玩一个游戏--积木小赛. Alice 和 Bob 初始时各有 n 块积木从左至右排成一排,每块积木都被标上了一个英文小写字母. Alice 可以从自己的积木中丢 ...

随机推荐

  1. Flutter Hackathon 2020

    Flutter Hackathon 2020 https://flutterhackathon.com/#/ Flutter Day https://mp.weixin.qq.com/s/ux17-A ...

  2. DoH & DNS over HTTPS

    DoH & DNS over HTTPS DNS over HTTPS(DoH)服务 http://mozilla.com.cn/thread-422231-1-1.html https:// ...

  3. 2020 web developer roadmap

    2020 web developer roadmap https://github.com/kamranahmedse/developer-roadmap https://roadmap.sh/ ht ...

  4. js 动态修改页面文本字体

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. nasm astrrchr函数 x86

    xxx.asm %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export ast ...

  6. JavaScript数据类型判断的四种方法

    码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14416375.html 本文分享了JavaScript类型判断的四种方法:typeo ...

  7. C++算法代码——单词查找

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=2472 题目描述 给定 n 个长度不超过 50 的由小写英文字母组成的单词准备查询,以 ...

  8. 深入浅出的JS执行机制(图文教程)

    前序 作为一个有理想有抱负的前端攻城狮,想要走向人生巅峰,我们必须将我们使用的功法练到天人合一的地步.我在们日常工作中,使用最多的语言就是JavaScript了,为了写出完美的.能装逼的代码,我们必须 ...

  9. SpringBoot以war包形式部署到外部Tomcat

    SpringBoot 项目打包时能打成 .jar 与 .war包文件,.jar使用 java -jar xx.jar 就可以启动,而 .war 可以部署到tomcat的 webapps 中,随tomc ...

  10. Linux fork()一个进程内核态的变化

    [前言]用户态的变化,耳熟能详不在赘述.现在支持读时共享,写时复制. 一.内核态的变化 1.fork一个子进程代码 #include <stdio.h> #include <stdl ...