문제 설명(링크):
https://www.acmicpc.net/problem/15261
15261번: Donut Drone
The first line contains two integers r and c (3 ≤ r, c ≤ 2 000) — the number of rows and the number of columns of the toroidal grid. The i-th of the following r lines contains a sequence of c integers ei,1, ei,2, . . . , ei,c (1 ≤ ei,j ≤ 109) —
www.acmicpc.net
문제 요약
도넛처럼 생긴 격자판에서 각각의 높이가 있는데, 한 칸에서 시작할 때 다음으로 갈 칸은 왼쪽 위, 왼쪽, 왼쪽 아래 중 높이가 가장 높은 곳이다.(세 값은 항상 다르다) 두 가지 쿼리를 처리해야 하는데, 하나는 어떤 칸의 높이가 변화하는 것이고, 하나는 특정 위치에서 $K$번 이동시키는 것이다.
풀이
일단 1열 i행에서 시작해 한바퀴를 돌아 다시 1열로 온 상황을 생각해 보자. 그러면, 다시 돌아왔을때의 행 번호을 $p_i$라 하자. 만약 $p_i$를 알고, update가 없다면 문제를 풀 수 있다. 일단 1열까지 이동하는 것은 일일히 시뮬레이션하고, $K$가 $C$보다 작으면 시뮬레이션한다. $K$가 $C$보다 크다면, 1열 $i$행에서 $C$번 이동하면 1열 $p_i$행으로 온다는 사실을 이용해 빠르게 시뮬레이션할 수 있다. $p$에서 발생하는 사이클($p_{a_1}=a_2, p_{a_2}=a_3, \cdots , p_{a_k}=a_1$과 같은 반복)을 찾을 때까지 반복하면 된다. 이때 시간복잡도는 $O(R+C)$이다.(사이클 크기가 최대 $R$, 시뮬레이션은 최대 $2C$번 해야 함)
이제 p를 update해야 한다. 하나의 관찰이 필요하다. 우선, x행 1열에서 시작해서 다시 1열로 돌아오는 과정에서 지나가는 칸들에 $x$를 기록해 두자. 이 과정을 모든 x에 대해 반복하면 각 칸에 적힌 숫자는 하나의 구간(원호에서의 구간으로, $l>r$일때 $[l, r]=\{ l, l+1,\cdots, R, 1, 2, \cdots r \}$을 이룬다는 것을 알 수 있다. 또한, 한 칸의 값이 바뀌면 그 칸 아래로 두개, 위로 두개의 칸에 적힌 구간이 변한다는 것을 관찰할 수 있다. 그러면 그 5개의 칸에 대해 그 칸에서 출발해서 갈 수 있는 칸들도 전부 바뀐다. 따라서 그 5개의 칸에 대해서 그 칸에서 시작해서 1열로 도달할 때까지 거쳐가는 칸들을 갱신해 주면, 많아야 $5M$개의 칸들의 구간만을 새로 갱신해야 한다. 따라서 매 업데이트마다 이를 실행해주고, 구간들을 통해 $p_i$도 새로 만들 수 있다.
소스코드
#include <bits/stdc++.h>
#include <cassert>
using namespace std;
typedef long long ll;
#define MAX 2050
#define ln '\n'
#define bb ' '
ll N, M;
ll X, Y;
struct range {
ll l;
ll r;
range() :l(0), r(0) {}
range(ll l, ll r) :l(l), r(r) {}
};
range operator+(const range r1, const range r2) {
if (r1.l == 0) return r2;
if (r2.l == 0) return r1;
range ret;
if (r1.r + 1 == r2.l || (r1.r == N || r2.l == 1)) ret = range(r1.l, r2.r);
else ret = range(r2.l, r1.r);
if (ret.r + 1 == ret.l) ret.l = 1, ret.r = N;
return ret;
}
range r[MAX][MAX];
ll mp[MAX][MAX];
ll dir[MAX][MAX]; //-1, 0, 1
ll p[MAX]; //permutation
ll f(ll x, ll y) {
return (x - 1) * M + y - 1;
}
ll pv(ll x) {
x--;
if (!x) x += M;
return x;
}
void set_dir(ll x, ll y) {
ll a, b, c;
a = mp[(x - 1) > 0 ? (x - 1) : N][y % M + 1];
b = mp[x][y % M + 1];
c = mp[x % N + 1][y % M + 1];
if (max({ a, b, c }) == a) dir[x][y] = -1;
if (max({ a, b, c }) == b) dir[x][y] = 0;
if (max({ a, b, c }) == c) dir[x][y] = 1;
}
void recalc(ll x, ll y) {
ll pp = pv(y);
r[x][y] = range();
if (dir[(x - 1) > 0 ? (x - 1) : N][pp] == 1) r[x][y] = r[x][y] + r[(x - 1) > 0 ? (x - 1) : N][pp];
if (dir[x][pp] == 0) r[x][y] = r[x][y] + r[x][pp];
if (dir[x % N + 1][pp] == -1) r[x][y] = r[x][y] + r[x % N + 1][pp];
}
void prop(ll x, ll y) {
if (y == M + 1) return;
ll ne = x % N + dir[x][y];
if (ne <= 0) ne += N;
recalc(ne, y + 1);
prop(ne, y + 1);
}
void update(int R, int C, int V) {
ll i, j;
mp[R][C] = V;
ll pvc = C - 1;
if (pvc < 1) pvc = M;
ll pvpv;
pvpv = R - 1;
if (pvpv <= 0) pvpv = N;
set_dir(pvpv, pvc);
set_dir(R, pvc);
set_dir(R % N + 1, pvc);
ll ppp = R - 3;
ll nc;
nc = C;
if (nc == 1) nc += M;
if (ppp <= 0) ppp += N;
for (i = 0; i < 7; i++) {
recalc(ppp, nc);
ppp = ppp % N + 1;
}
ppp = R - 3;
if (ppp <= 0) ppp += N;
for (i = 0; i < 7; i++) {
prop(ppp, nc);
ppp = ppp % N + 1;
}
for (i = 1; i <= N; i++) {
if (!(r[i][M + 1].l)) continue;
for (j = r[i][M + 1].l; j != r[i][M + 1].r; j = j % N + 1) {
p[j] = i;
}
p[r[i][M + 1].r] = i;
}
return;
}
ll qcnt;
void simulate(int K) {
qcnt++;
while (Y != 1) {
X = X % N + dir[X][Y];
if (X <= 0) X += N;
Y = Y % M + 1;
K--;
if (!K) break;
if (Y == 1) break;
}
if (!K) {
cout << X << bb << Y << ln;
return;
}
vector<ll> chk(N + 1, 0);
ll i = 1;
chk[X] = 1;
while (K > (M * N)) {
K -= M;
i++;
X = p[X];
if (K <= 2 * N * M) break;
if (chk[X] && (K > (2 * N * M))) {
ll d = i - chk[X];
K %= (d * M);
break;
}
else chk[X] = i;
}
//assert(qcnt <= 3000);
while (K >= M) {
X = p[X];
K -= M;
}
for (i = 0; i < K; i++) {
X = X % N + dir[X][Y];
if (X <= 0) X += N;
Y = Y % M + 1;
}
cout << X << bb << Y << ln;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
X = Y = 1;
cin >> N >> M;
ll i, j;
for (i = 1; i <= N; i++) r[i][1] = range(i, i);
for (i = 1; i <= N; i++) {
for (j = 1; j <= M; j++) {
cin >> mp[i][j];
}
}
for (i = 1; i <= N; i++) {
for (j = 1; j <= M; j++) {
set_dir(i, j);
}
}
for (i = 1; i <= N; i++) prop(i, 1);
for (i = 1; i <= N; i++) {
if (!(r[i][M + 1].l)) continue;
for (j = r[i][M + 1].l; j != r[i][M + 1].r; j = j % N + 1) {
p[j] = i;
}
p[r[i][M + 1].r] = i;
}
ll Q;
cin >> Q;
while (Q--) {
string s;
cin >> s;
if (s[0] == 'm') {
ll a;
cin >> a;
simulate(a);
}
else {
ll a, b, c;
cin >> a >> b >> c;
update(a, b, c);
}
}
}
총평
좀 난이도 있는 문제였다. 또 어떤 대회에서 이 문제를 봤는데, 대회장에서 풀이를 얻은(M과 N를 바꿔써서 풀지는 못했다) 가장 어려운 문제이다. D4~D3 정도가 적절한 것 같다.
'문제풀이' 카테고리의 다른 글
백준 15776 New Home (APIO 2018 A번) (0) | 2021.09.03 |
---|---|
백준 18847. Stray Cat (JOISC 2019/2020 Day 3 3번) (0) | 2021.09.01 |
백준 16977 히스토그램에서 가장 큰 직사각형과 쿼리 (0) | 2021.04.19 |
BOJ 1017 소수 쌍 (0) | 2020.10.28 |
BOJ 1321 군인 (0) | 2020.10.27 |