티스토리 뷰
이제 D4가 너무 적게 (3문제, 문제 오류 제외하면 2문제) 남은 관계로 D3도 scope에 포함시키기로 했다. 현재 D4+D3은 18문제가 남아 있다.


기술적인 문제로 인해 한 문제 (9467)는 캡처에서는 빠져 있다.
새로 추가된 다이아 3 문제에 대한 인상은 다음과 같다. 제목과 기억에만 의존한 내용이다.
- 누가 봐도 Matkor 컵인 문제가 하나 보인다. 얘는 좀 미뤄두는 게 정신건강에 좋을 것 같다.
- 구데기컵 문제도 하나 보이는데, 얘도 미뤄두는 게 정신건강에 좋을 것 같다.
- 마지막 문제인 레이저는 매우 최근 대회에서 시간이 부족해 디버깅을 하지 못한 문제이다. 풀이를 알기 때문에 크게 어렵지 않게 풀 수 있을 거라고 기대한다.
- 수쿼 문제 비슷한 게 보이는데, 얘도 수쿼과라면 빠르게 풀 수 있을 거라고 기대한다.
남은 문제: 65 → 60문제
BOJ 9467. 문자열 경로
문제를 봤는데 그렇게 어려워 보이지 않아서 생각해 봤고 실제로도 그리 어렵지 않았다. 두부 모판 자르기라는 문제와 별 차이 없이 풀면 된다. 원래 D3이었는데 P2를 줘서 D5로 떨어졌다.
#include <iostream>
using namespace std;
typedef long long ll;
const ll MOD = 1'000'000'009;
int n, m;
char s[20], t[20];
ll DP[100][1<<16];
inline void update(int r, int c, int mask, ll val){
DP[r*m + c][mask] = (DP[r*m + c][mask] + val) % MOD;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> m >> s >> t;
if(s[0] != t[0]){
cout << 0;
return 0;
}
DP[0][3] = 1;
int MX = (1<<(m*2)) - 1;
for(int cell=1; cell<n*m; cell++){
int r = cell / m, c = cell % m, d = r+c; // cell1 -> cell 전이
for(int mask=0; mask<=MX; mask++){
int up = (mask >> (m*2-2)) & 3, left = mask & 3;
if(r == 0) up = 0;
if(c == 0) left = 0;
int us = (up >> 1) & 1, ut = up & 1;
int ls = (left >> 1) & 1, lt = left & 1;
ll val = DP[cell-1][mask];
if(s[d] != t[d]){ // 하나만 가능
if(us || ls) update(r, c, ((mask<<2) & MX) | 2, val);
if(ut || lt) update(r, c, ((mask<<2) & MX) | 1, val);
update(r, c, ((mask<<2) & MX), val * (26 - int(us || ls) - int(ut || lt)) % MOD);
}
else{ // 둘 다 가능
if(us || ls || ut || lt){
update(r, c, ((mask<<2) & MX) | (us || ls ? 2 : 0) | (ut || lt ? 1 : 0), val);
}
update(r, c, ((mask<<2) & MX), val * (26 - int(us||ls||ut||lt)) % MOD);
}
}
}
ll ans = 0;
for(int i=0; i<=MX; i++) if((i & 3) == 3) ans = (ans + DP[n*m-1][i]) % MOD;
cout << ans;
}
BOJ 35035. 레이저
대회 때 풀이는 찾았는데 시간이 없어서 디버깅을 못 한 문제다. 중요한 관찰은 문제의 상황을 세그먼트 트리로 관리할 수 있다는 것이다. 특정 $x$ 구간을 통과한 후 초기 $y$값 -> 나중 $y$값과 이동 거리는 slope trick 비슷한 식으로 생겼는데, 이 형태가 둘 중 하나라서 세그먼트 트리로 합쳐줄 수 있다. 이렇게 하고 나면 구현 실수 없이 각 케이스를 처리하는 문제가 된다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct segTree{
struct Node{
bool mode; // 0: [l, r] / 1: l
ll l, r, p, v;
Node(){}
Node(bool mode, ll l, ll r): mode(mode), l(l), r(r), p(0), v(0){}
Node(bool mode, ll l, ll p, ll v): mode(mode), l(l), r(0), p(p), v(v){}
void print(int i=-1){
if(i>-1) cout << "[Node " << i << "] ";
if(!mode) cout << "[T0] l: " << l << ", r: " << r << "\n";
else cout << "[T1] l: " << l << ", p: " << p << ", v: " << v << "\n";
}
inline ll pos(ll x){
if(!mode) return max(l, min(r, x));
else return l;
}
inline ll dist(ll x){
if(!mode) return (x < l ? l-x : x > r ? x-r : 0);
else return abs(x-p) + v;
}
friend Node operator+(const Node a, const Node b){
if(a.mode == 0 && b.mode == 0){
if(a.r <= b.l) return Node(1, b.l, a.r, b.l - a.r);
else if(b.r <= a.l) return Node(1, b.r, a.l, a.l - b.r);
else return Node(0, max(a.l, b.l), min(a.r, b.r));
}
else if(a.mode == 0 && b.mode == 1){
if(a.l <= b.p && b.p <= a.r) return Node(1, b.l, b.p, b.v);
else if(b.p < a.l) return Node(1, b.l, a.l, a.l-b.p + b.v);
else return Node(1, b.l, a.r, b.p-a.r + b.v);
}
else if(a.mode == 1 && b.mode == 0){
if(b.l <= a.l && a.l <= b.r) return a;
else if(a.l < b.l) return Node(1, b.l, a.p, b.l - a.l + a.v);
else return Node(1, b.r, a.p, a.l - b.r + a.v);
}
else if(a.mode == 1 && b.mode == 1){
return Node(1, b.l, a.p, abs(a.l - b.p) + a.v + b.v);
}
else exit(1);
}
} tree[1<<20];
void init(int i, int l, int r, ll *A, ll *B){
if(l==r){
tree[i] = Node(0, A[l], B[l]);
// tree[i].print(i);
return;
}
int m = (l+r)>>1;
init(i*2, l, m, A, B);
init(i*2+1, m+1, r, A, B);
tree[i] = tree[i*2] + tree[i*2+1];
// tree[i].print(i);
}
void update(int i, int l, int r, int x, ll a, ll b){
if(l==r){
tree[i] = Node(0, a, b);
// tree[i].print(i);
return;
}
int m = (l+r)>>1;
if(x <= m) update(i*2, l, m, x, a, b);
else update(i*2+1, m+1, r, x, a, b);
tree[i] = tree[i*2] + tree[i*2+1];
}
Node query(int i, int l, int r, int s, int e){
if(s<=l && r<=e) return tree[i];
int m = (l+r)>>1;
if(e<=m) return query(i*2, l, m, s, e);
else if(m<s) return query(i*2+1, m+1, r, s, e);
else return query(i*2, l, m, s, e) + query(i*2+1, m+1, r, s, e);
}
} tree;
int n, q; ll L;
ll A[300002], B[300002];
int main(){
#ifndef LOCAL
ios_base::sync_with_stdio(false);
cin.tie(NULL);
#endif
cin >> n >> L >> q;
for(int i=1; i<=n; i++) cin >> A[i];
for(int i=1; i<=n; i++){
cin >> B[i];
B[i] = L - B[i];
}
tree.init(1, 1, n, A, B);
while(q--){
int qt;
cin >> qt;
if(qt==1){
ll xs, ys, xt, yt;
cin >> xs >> ys >> xt >> yt;
if(xs == xt) cout << abs(ys - yt) << '\n';
else{
if(xs > xt) swap(xs, xt), swap(ys, yt);
xs++;
segTree::Node p = tree.query(1, 1, n, xs, xt);
cout << p.dist(ys) + abs(p.pos(ys) - yt) + (xt - xs + 1) << '\n';
}
}
else{
int x; ll a, b;
cin >> x >> a >> b;
b = L-b;
tree.update(1, 1, n, x, a, b);
}
}
}
BOJ 17191. Valleys
대략적인 풀이 방식은 다음과 같다. 어떤 격자칸 집합이 non-holey region일 조건은,
- 해당 격자칸 집합이 edge-connected이고,
- 해당 격자칸 집합을 그래프로 만들었을 때, 즉 각 격자칸을 점으로, edge-인접한 격자칸들을 간선으로 이은 그래프를 생각했을 때, 모든 면은 $2 \times 2$ 격자칸에서 만들어진 사각형이어야 한다.
여기까지 생각하고 난다면 특정한 방향으로 진행하며 V와 E의 크기를 union find를 통해 관리하고, 남은 F가 예상치와 맞는지 비교하면 된다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct dat{
int x, y, v;
dat(){}
dat(int x, int y, int v): x(x), y(y), v(v){}
bool operator<(const dat r)const{
return v<r.v;
}
};
int n;
int A[752][752];
int B[752][752];
struct UnionFind{
int par[600002];
ll V[600002], E[600002], F[600002];
void init(int n){
for(int i=0; i<=600'000; i++) par[i] = i, V[i] = E[i] = F[i] = 0;
}
int find(int x){
if(x==par[x]) return x;
return par[x] = find(par[x]);
}
void merge(int x, int y){
x = find(x), y = find(y);
if(x==y) return;
par[y] = x;
V[x] += V[y], E[x] += E[y], F[x] += F[y];
}
} dsu;
inline int get(int x, int y){
return (x-1)*n+(y-1);
}
const int xx[]={0, 1, 0, -1}, yy[]={1, 0, -1, 0};
void addPoint(int x, int y){
int id = get(x, y);
// uf에 반영
B[x][y] = 1;
for(int d=0; d<4; d++){
int nx = x+xx[d], ny = y+yy[d];
if(nx<1||nx>n||ny<1||ny>n) continue;
if(B[nx][ny]) dsu.merge(id, get(nx, ny));
}
// v, e, f 업데이트
int where = dsu.find(id);
dsu.V[where]++;
for(int d=0; d<4; d++){
int nx = x+xx[d], ny = y+yy[d];
if(nx<1||nx>n||ny<1||ny>n) continue;
if(B[nx][ny]) dsu.E[where]++;
}
for(int a=0; a<2; a++) for(int b=0; b<2; b++){
int xl = x+a-1, xr = x+a;
int yl = y+b-1, yr = y+b;
if(xl<1||xr>n||yl<1||yr>n) continue;
if(B[xl][yl] && B[xl][yr] && B[xr][yl] && B[xr][yr]) dsu.F[where]++;
}
}
ll ans;
void check(int x, int y, int id){
int idx = get(x, y);
idx = dsu.find(idx);
int V = dsu.V[idx], E = dsu.E[idx], F = dsu.F[idx];
if(V - E + F == 1) ans += V;
}
int main(){
#ifndef LOCAL
ios_base::sync_with_stdio(false);
cin.tie(NULL);
#endif
cin >> n;
vector<dat> vec;
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++){
cin >> A[i][j];
vec.push_back({i, j, A[i][j]});
}
sort(vec.begin(), vec.end());
dsu.init(n*n);
for(int l=0; l<(int)vec.size(); l++){
addPoint(vec[l].x, vec[l].y);
check(vec[l].x, vec[l].y, vec[l].v);
}
cout << ans;
}
BOJ 8128. Subway
뭔가 그리디하게 하면 되겠다는 느낌은 있었는데 ML이 128MB라 조금 걱정이었다. 처음에 생각했던 풀이는 가장 작은 가지부터 쳐내면서 $2k$개의 리프 노드만 남기는 것이었는데, 시간 복잡도 자체는 $O(n \log n)$ 같아 보였지만 $n$도 크고 ML도 작아서 이게 될지 확신이 없었다. 그러다가 온라인에 풀이 검색을 해 보았고 풀이가 트리의 지름을 사용한 다른 방향이라는 걸 알게 되었다.
그래도 궁금하니 기존에 생각했던 풀이가 작동하는지 짜 보기로 했다. 구현을 생각하던 중 결국 내 그리디 알고리즘을 사용하면 트리의 지름이 남을 수밖에 없다는 사실을 깨달았다. 그렇다면 트리의 지름 하나를 아무렇게나 잡아 살려 두는 것으로 고정하고, 나머지 가지들 중에서 그리디하게 긴 쪽으로 뽑아 주는 식으로 하면 구현이 편해진다는 것을 알게 되었다.
이렇게 생각하니 구현이 크게 어렵지 않았다. $O(n \log n)$에 풀 수 있다. 로그 factor는 마지막 정렬 한 번에서만 사용된다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, k;
vector<int> edge[1000002];
int dist[1000002];
bool onPath[1000002];
void dfs(int x, int p=-1){
for(int y: edge[x]){
if(y==p) continue;
dist[y] = dist[x] + 1;
dfs(y, x);
}
}
bool findPath(int x, int p, int target, vector<int> &path){
if(x==target){
path.push_back(x);
return true;
}
path.push_back(x);
for(int y: edge[x]){
if(y==p) continue;
if(findPath(y, x, target, path)) return true;
}
path.pop_back();
return false;
}
vector<int> branch;
int dfs2(int x, int p=-1){
vector<int> childs;
for(int y: edge[x]){
if(y==p || onPath[y]) continue;
childs.push_back(dfs2(y, x) + 1);
}
if(childs.empty()) return 0;
auto it = max_element(childs.begin(), childs.end());
int idx = it - childs.begin();
swap(childs[idx], childs[childs.size()-1]);
int tmp = childs.back(); childs.pop_back();
for(int v: childs) branch.push_back(v);
return tmp;
}
int main(){
#ifndef LOCAL
ios_base::sync_with_stdio(false);
cin.tie(NULL);
#endif
cin >> n >> k;
k = min(2*k, n);
if(!k){
cout << 0;
return 0;
}
for(int i=1; i<n; i++){
int x, y;
cin >> x >> y;
edge[x].push_back(y);
edge[y].push_back(x);
}
int leafCnt = 0;
for(int i=1; i<=n; i++) {
if(edge[i].size() == 1) leafCnt++;
}
if(leafCnt <= k){
cout << n;
return 0;
}
int remove = leafCnt - k;
// 지름 뽑기
dfs(1);
int d1 = max_element(dist+1, dist+n+1) - dist;
fill(dist+1, dist+n+1, 0);
dfs(d1);
int d2 = max_element(dist+1, dist+n+1) - dist;
vector<int> path;
findPath(d1, -1, d2, path);
int L = (int)path.size() - 1;
for(int x: path) onPath[x] = true;
for(int x: path){
int tmp = dfs2(x);
if(tmp) branch.push_back(tmp);
}
sort(branch.rbegin(), branch.rend());
int ans = n;
while(remove--){
ans -= branch.back();
branch.pop_back();
}
cout << ans;
}
BOJ 4223. Mummy Madness
일단 parametric search를 쓰자. 시간 $s$ 동안 주인공이 살아남을 수 있는지 본다. 주인공을 중심으로 한 $(2s+1) \times (2s+1)$ 영역이 주인공이 시간 동안 도달할 수 있는 영역이다. 비슷한 방식으로 각 미라가 시간 동안 도달할 수 있는 영역도 정의된다. 이제 시간 $s$ 동안 미라들이 접근 가능한 영역이 시간 $s$ 동안 주인공이 접근 가능한 영역을 전부 덮는지를 보면 된다. 모두 덮는다면 미라들이 항상 잡을 수 있고, 모두 덮지 못한다면 주인공이 도피 가능한 루트가 항상 존재한다.
여기까지 왔다면 저걸 짜면 테스트 케이스당 $O(n \log n \log X)$에 문제를 해결할 수 있다. 다만 여기서 끝내면 조금 찜찜하니 증명을 시도해보기로 했다.
보여야 할 조건은 (직사각형이 완전히 덮인다) -> (잡을 수 있다), (직사각형이 완전히 덮이지 않는다) -> (잡을 수 없다)이다. 후자가 조금 더 쉽다. 시간 $t$에도 직사각형이 완전히 덮이지 않으면, 귀납적으로 시간 $t-1$에도 잡히지 않는 칸이 있어야 함을 알 수 있다. 이런 식으로 $t=0$까지 진행하면 어떤 루트로 도망갈 수 있는지 알 수 있다.
(직사각형이 완전히 덮인다) -> (잡을 수 있다)는 이런 식으로 보일 수 있다. 시간 $t$에 직사각형이 완전히 덮인다고 하자. 주인공의 최종 위치 $(x, y)$를 덮는 어떤 미라 $p$를 생각하자. 이때 미라 $p$가 시간 $t$ 후 무조건 $(x, y)$에 도착한다는 것을 보이면 된다. 핵심은 주인공이 갈 수 있는 칸의 집합이 점점 줄어든다는 것과, 미라가 $(x, y)$에 시간 $t$ 이내에 갈 수 있다는 성질이 보존된다는 것이다. 조금 생각해 본다면 주인공이 시간 $t$에 $(x, y)$에 도달했다면, 어떤 방법으로든 미라가 주인공을 향해 다가갈 때 미라가 시간 $t$에 도착할 수 있는 영역에서 $(x, y)$가 빠질 수 없다는 것을 알 수 있다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve(int tc);
int main(){
#ifndef LOCAL
ios::sync_with_stdio(false);
cin.tie(NULL);
#endif
for(int tc=1; ; tc++){
solve(tc);
}
}
int n;
ll X[100002], Y[100002];
ll xl[100002], xr[100002], yl[100002], yr[100002];
struct segTree{
int minV[1<<20], minC[1<<20];
int lazy[1<<20];
void init(int i, int l, int r){
minV[i] = 0, minC[i] = r-l+1;
lazy[i] = 0;
if(l==r) return;
int m = (l+r)>>1;
init(i*2, l, m);
init(i*2+1, m+1, r);
}
void propagate(int i, int l, int r){
minV[i] += lazy[i];
if(l!=r) lazy[i*2] += lazy[i], lazy[i*2+1] += lazy[i];
lazy[i] = 0;
}
void merge(int i){
minV[i] = min(minV[i*2], minV[i*2+1]);
minC[i] = (minV[i] == minV[i*2] ? minC[i*2] : 0) + (minV[i] == minV[i*2+1] ? minC[i*2+1] : 0);
}
void update(int i, int l, int r, int s, int e, int v){
propagate(i, l, r);
if(r<s || e<l) return;
if(s<=l && r<=e){
lazy[i] += v;
propagate(i, l, r);
return;
}
int m = (l+r)>>1;
update(i*2, l, m, s, e, v);
update(i*2+1, m+1, r, s, e, v);
merge(i);
}
pair<ll, ll> query(int i, int l, int r){
propagate(i, l, r);
return make_pair(minV[i], minC[i]);
}
} tree;
struct Query{
ll y;
int xl, xr, v;
Query(){}
Query(ll y, int xl, int xr, int v): y(y), xl(xl), xr(xr), v(v) {}
bool operator<(const Query &r) const{
return y < r.y;
}
};
bool able(ll D){
for(int i=1; i<=n; i++){
if(abs(X[i]) > 2*D || abs(Y[i]) > 2*D) continue;
xl[i] = X[i] - D, xr[i] = X[i] + D;
yl[i] = Y[i] - D, yr[i] = Y[i] + D;
xl[i] = max(-D, min(D, xl[i])), xr[i] = max(-D, min(D, xr[i]));
yl[i] = max(-D, min(D, yl[i])), yr[i] = max(-D, min(D, yr[i]));
}
vector<ll> vx = {-D, D};
for(int i=1; i<=n; i++){
if(abs(X[i]) > 2*D || abs(Y[i]) > 2*D) continue;
vx.push_back(xl[i]), vx.push_back(xr[i]);
if(xl[i] > -D) vx.push_back(xl[i]-1);
if(xr[i] < D) vx.push_back(xr[i]+1);
}
sort(vx.begin(), vx.end()); vx.erase(unique(vx.begin(), vx.end()), vx.end());
for(int i=1; i<=n; i++){
if(abs(X[i]) > 2*D || abs(Y[i]) > 2*D) continue;
xl[i] = lower_bound(vx.begin(), vx.end(), xl[i]) - vx.begin() + 1;
xr[i] = lower_bound(vx.begin(), vx.end(), xr[i]) - vx.begin() + 1;
}
vector<Query> vec;
for(int i=1; i<=n; i++){
if(abs(X[i]) > 2*D || abs(Y[i]) > 2*D) continue;
vec.push_back(Query(yl[i], xl[i], xr[i], 1));
vec.push_back(Query(yr[i]+1, xl[i], xr[i], -1));
}
sort(vec.begin(), vec.end());
if(vec.empty()) return true;
int k = (int)vx.size();
assert(k <= 500'000);
ll prvy = -D;
tree.init(1, 1, k);
for(Query &p: vec){
if(prvy != p.y){
pair<ll, ll> tmp = tree.query(1, 1, k);
if(tmp.first == 0) return true;
prvy = p.y;
}
tree.update(1, 1, k, p.xl, p.xr, p.v);
}
if(prvy <= D) return true;
return false;
}
void solve(int tc){
cin >> n;
if(n == -1) exit(0);
for(int i=1; i<=n; i++) cin >> X[i] >> Y[i];
ll MIN = 0, MAX = 3e6, ANS = 0;
while(MIN <= MAX){
ll MID = (MIN + MAX) / 2;
if(able(MID)){
ANS = MID;
MIN = MID + 1;
}
else{
MAX = MID - 1;
}
}
cout << "Case " << tc << ": ";
if(ANS >= 3e6) cout << "never\n";
else cout << (ANS+1) << "\n";
}
'시리즈 > 과거 청산' 카테고리의 다른 글
| 과거 청산 챌린지 #6 (0) | 2026.02.09 |
|---|---|
| 과거 청산 챌린지 #5 (0) | 2026.01.28 |
| 과거 청산 챌린지 #4 (0) | 2026.01.24 |
| 과거 청산 챌린지 #2 (0) | 2026.01.14 |
| 과거 청산 챌린지 #1 (0) | 2026.01.11 |
