티스토리 뷰

지난번에 이어 다이아 4를 계속 풀고 있다.

 

남은 문제들이 대부분 풀기 싫게 생겼지만, 이런 문제들을 푸는 연습도 중요한 것 같다.

 

남은 문제: 69 → 65문제

BOJ 8310. Riddle

간단한 2SAT 문제이지만 문제에 등장하는 그래프의 크기가 너무 크다는 게 문제다. atcoder의 SCC 라이브러리를 이용해 짜니 2초 정도에 통과했다.

#include <bits/stdc++.h>
#include <atcoder/scc>

using namespace std;
using namespace atcoder;

typedef long long ll;

int n, m, k;
vector<int> county[1'000'005];
int where[1'000'005];
int idx[1'000'005], ord[1'000'005];
int sccNum[4'000'005];
int ans[1'000'005], has[1'000'005], which[1'000'005]; // ans 1: true, 2: false

int main(){
    #ifndef LOCAL
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    #endif

    cin >> n >> m >> k;

    scc_graph G (n*4); // 0: true, 1: false, 2: L, 3: R
    for(int i=1; i<=m; i++){
        int x, y;
        cin >> x >> y;
        x--, y--;
        G.add_edge(n+x, y);
        G.add_edge(n+y, x);
    }

    int cnt = 0;
    for(int i=0; i<k; i++){
        int w;
        cin >> w;
        county[i].resize(w);

        int L = cnt;
        for(int j=0; j<w; j++){
            cin >> county[i][j];
            county[i][j]--;
            idx[county[i][j]] = cnt;
            ord[cnt] = county[i][j];
            where[county[i][j]] = i;
            cnt++;
        }
        int R = cnt - 1;

        for(int j=L; j<=R; j++){
            if(j>L) G.add_edge(ord[j], n*2+j-1);
            if(j<R) G.add_edge(ord[j], n*3+j+1);
            G.add_edge(n*2+j, n+ord[j]);
            G.add_edge(n*3+j, n+ord[j]);
        }
        for(int j=L; j<R; j++){
            G.add_edge(n*2+j+1, n*2+j);
            G.add_edge(n*3+j, n*3+j+1);
        }
    }

    vector<vector<int>> scc = G.scc();
    for(int i=0; i<(int)scc.size(); i++){
        // cout << "SCC " << i << ": ";
        for(int v : scc[i]){
            sccNum[v] = i;
            // cout << v << ' ';
        }
        // cout << '\n';
    }

    for(int i=0; i<n; i++){
        if(sccNum[i] == sccNum[i+n]){
            cout << "NIE";
            return 0;
        }
    }

    for(vector<int> &v: scc){
        bool canFalse = true;
        for(int x: v){
            if((x < n && ans[x] == 1) || (n <= x && x < 2*n && ans[x-n] == 2)){
                canFalse = false;
                break;
            }
        }

        if(canFalse){
            for(int x: v){
                if(x < n) ans[x] = 2;
                else if(n <= x && x < 2*n) ans[x-n] = 1, has[where[x-n]] = 1, which[where[x-n]] = x-n;
            }
        }
        else{
            for(int x: v){
                if(x < n) ans[x] = 1, has[where[x]] = 1, which[where[x]] = x;
                else if(n <= x && x < 2*n) ans[x-n] = 2;
            }
        }
    }

    cout << "TAK\n";
    for(int i=0; i<k; i++){
        cout << (has[i] ? which[i]+1 : county[i][0]+1) << ' ';
    }
}

BOJ 15288. Final Level

문제를 보면 알겠지만 그다지 어려워 보이는 문제는 아니다. 다만 다양한 케이스 처리를 실수 없이 깔끔하게 하는 것이 관건인 문제이다.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

void solve(){
    ll x, y, n;
    cin >> x >> y >> n;

    ll flipH = 1, flipV = 1;
    if(x < 0) x = -x, flipH = -1;
    if(y < 0) y = -y, flipV = -1;

    ll MIN = 0, MAX = 1e9, ANS = 1e9;
    ll P = n, Q = n-1;
    while(MIN <= MAX){
        ll MID = (MIN + MAX) / 2;
        if(max(x, y) <= P*MID-1 && x + y <= (P+Q) * MID - 1) ANS = MID, MAX = MID - 1;
        else MIN = MID + 1;
    }

    vector<pair<ll, ll> > ans;
    ll cx = 0, cy = 0;
    for(int turn=1; turn<=ANS; turn++){
        ll nx = x - cx, ny = y - cy;
        ll px, py;

        bool dir = nx >= ny;
        if(turn == ANS){
            if(ny == n || nx == 0) dir = 0;
            else dir = 1;
        }
        if(dir){
            if(turn > 1) cx++;
            px = cx, py = min(cy+n-1, y);
            cx = px + n - 1, cy = py;
        }
        else{
            if(turn > 1) cy++;
            py = cy+n-1, px = cx;
            cy = py, cx = min(cx+n-1, x);
        }
        ans.push_back(make_pair(px, py));
    }

    cout << (int)ans.size() << '\n';
    for(auto [x, y]: ans){
        ll verX = x, verY = y - n + 1;
        ll horX = x + n - 1, horY = y;
        if(flipH == -1) verX = -verX, horX = -horX;
        if(flipV == -1) verY = -verY, horY = -horY;
        cout << verX << ' ' << verY << ' ' << horX << ' ' << horY << '\n';
    }
}

int main(){
    #ifndef LOCAL
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    #endif

    int t;
    cin >> t;
    while(t--){
        solve();
    }
}

BOJ 28007. LaLa and Magic Stone

문제를 보면 블록을 뭔가 그리디하게 배치하는 방식으로 풀 수 있을 것 같은 느낌이 든다. 정확히는 저것 말고 풀 수 있는 방법이 없을 것 같은 느낌이 든다. 대각선 방향으로 훑으면서 그리디하게 채우려고 노력한다면, 조금 많은 케웍을 통해 그리디한 전략을 만들 수 있다. 정확히는 주변 일부 칸들을 보면서 어떤 방향으로 채워야 하는지가 거의 확정되고, 문제의 예제 1과 같이 2가지로 채울 수 있는 경우를 제외하고는 답이 유일하거나 없다.

#include <bits/stdc++.h>

using std::string;
using std::cin, std::cout;
using std::ios_base;

typedef long long ll;
const ll MOD = 998'244'353;

int n, m;
int arr[1002][1002];

ll ans = 1;

inline int get(int x, int y){
    if(x<1 || x>n || y<1 || y>m) return 0;
    return arr[x][y];
}

inline void set(int x, int y){
    if(x<1 || x>n || y<1 || y>m || !arr[x][y]){
        cout << 0;
        exit(0);
    }
    arr[x][y] = 0;
}

int xx[]={0, 1, 0, -1}, yy[]={1, 0, -1, 0}; // RDLU
inline void set(int x, int y, int dir){
    for(int i=-1; i<=1; i++) for(int j=-1; j<=1; j++){
        if(i==0 && j==0) continue;
        if(i==xx[dir] && j==yy[dir]) continue;
        set(x+i, y+j);
    }
}

void print(){
    cout << "Ans: " << ans << "\n";
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            cout << arr[i][j];
        }
        cout << "\n";
    }
}

int main(){
    #ifndef LOCAL
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    #endif

    cin >> n >> m;
    for(int i=1; i<=n; i++){
        string s;
        cin >> s;
        for(int j=0; j<m; j++){
            arr[i][j+1] = (s[j] == '0');
        }
    }

    for(int s=2; s<=n+m; s++){
        for(int r=1; r<=n; r++){
            int c = s-r;
            if(c<1 || c>m || !get(r, c)) continue;

            int d[4] = {get(r+1, c+2), get(r+2, c+1), get(r+1, c), get(r, c+1)};
            int cnt=d[0]+d[1]+d[2]+d[3];
            if(cnt < 3){
                cout << 0;
                return 0;
            }

            if(cnt == 3){
                set(r+1, c+1, !d[0] ? 0 : !d[1] ? 1 : !d[2] ? 2 : 3);
                // print();
                continue;
            }

            if(get(r+1, c+1)){ // 안쪽이 차 있는 경우
                int a = get(r+1, c-1);
                if(a){
                    ans = (ans * 2) % MOD;
                    set(r+1, c+1, 2);
                    set(r+2, c, 0);
                }
                else{
                    ans = (ans * 2) % MOD;
                    set(r+1, c+1, 0);
                    set(r+2, c+2, 2);
                }
            }
            else{
                int x = get(r-1, c+2);
                int b = get(r+2, c-1);
                int y = get(r+2, c+3);
                if(x){
                    set(r+1, c+1, 0);
                    set(r, c+3, 2);
                }
                else if(b){
                    set(r+1, c+1, 1);
                    set(r+3, c, 3);
                }
                else if(y){
                    set(r+1, c+1, 1);
                    set(r+3, c+2, 3);
                }
                else{
                    set(r+1, c+1, 0);
                    set(r+2, c+3, 2);
                }
            }
            // print();
        }
    }

    cout << ans;
}

BOJ 19838. Circuit

우선 회로를 직렬/병렬로 나눠 구성하기만 하면 자명한 DP가 된다. 각 성분에 대해 트리를 만드는 경우의 수와 트리에서 한 간선을 뺀 형태, 즉 n-2개의 간선으로 cut이 된 상태를 만드는 경우의 수를 구해 둔다. 그럼 직렬/병렬에 따라 서로가 서로를 호출하는 DP 형태가 만들어져서 문제가 쉬워진다.

 

다만 문제는 저 직렬/병렬을 구현하는 파트이다. 이 부분이 엄청나게 까다롭고 전체 문제의 난이도를 결정한다.

 

문제에서 주어진 것과 같은 그래프를 series-parallel graph라고 부른다. Wikipedia에서 알게 된 사실인데, 이런 그래프는 다음 두 과정 중 하나를 반복해서 적용하는 것으로 축약할 수 있다. (즉, 반복적으로 아래 연산 중 하나를 적용하여 노드 $2$개 (소스와 싱크), 간선은 $(s, t)$ 하나뿐인 그래프로 변경할 수 있다.)

  • 간선 $(u, v)$가 여러 개일 때, 하나만 남긴다.
  • (소스나 싱크가 아닌) 정점 $v$의 차수가 $2$이고 두 이웃 노드가 $a$, $b$인 경우, 간선 $(a, v)$와 $(b, v)$를 제거하고 간선 $(a, b)$를 추가한다.

위 과정은 std::set을 이용해 반복적으로 처리해줄 수 있고, 위 과정을 시행하며 동시에 트리 구조를 구조체로 구조화해주는 것이 어렵지 않다. 여기까지 하고 나면 위에서 설명한 쉬운 DP를 통해 문제를 해결할 수 있다.

 

다만 여기까지 생각해 놓고도 오랜 시간 동안 TLE를 받았는데, std::multimap에서 count 함수가 선형이 될 수 있다는 사실을 인지하지 못했다. 이 부분을 고치니 정답을 받았다.

#include <iostream>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")

using namespace std;

typedef long long ll;
const ll MOD = 998'244'353;

void input();
void encodeCircuit();
void solve();

int main(){
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    input();
    encodeCircuit();
    solve();
}

int n, m;
vector<int> edge[100005];

void input(){
    cin >> n >> m;
    for(int i=1; i<=m; i++){
        int u, v;
        cin >> u >> v;
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
}

int cm[300005], lc[300005], rc[300005], ccnt, root;

int newCircuit(){
    int idx = ++ccnt;
    cm[idx] = lc[idx] = rc[idx] = 0;
    return idx;
}
int newCircuit(int mode, int l, int r){
    int idx = ++ccnt;
    cm[idx] = mode;
    lc[idx] = l;
    rc[idx] = r;
    return idx;
}
pair<ll, ll> solveCircuit(int idx){
    if(cm[idx] == 0){
        return make_pair(1, 1);
    }
    pair<ll, ll> lp = solveCircuit(lc[idx]);
    pair<ll, ll> rp = solveCircuit(rc[idx]);
    if(cm[idx] == 1){
        return make_pair((lp.first * rp.first) % MOD,
                         (lp.first * rp.second + lp.second * rp.first) % MOD);
    }
    else{
        return make_pair((lp.first * rp.second + lp.second * rp.first) % MOD,
                         (lp.second * rp.second) % MOD);
    }
}

multimap<pair<int, int>, int> circuitMap;
map<pair<int, int>, int> circuitCnt;
set<pair<int, int>> multi;
multiset<pair<int, int>> adj;
int deg[100005];
set<int> deg2;
int totalCnt;

void insertEdge(int u, int v, int circuit = -1){
    if(u>v) swap(u, v);
    totalCnt++;

    circuitMap.insert({{u, v}, circuit != -1 ? circuit : newCircuit()});
    circuitCnt[{u, v}]++;
    if(circuitCnt.find({u, v}) != circuitCnt.end() && circuitCnt[{u, v}] == 2){
        multi.insert({u, v});
    }

    if(deg[u] == 2 && u!=1 && u!=n) deg2.erase(u);
    if(deg[v] == 2 && v!=1 && v!=n) deg2.erase(v);
    adj.insert({u, v});
    adj.insert({v, u});
    deg[u]++, deg[v]++;
    if(deg[u] == 2 && u!=1 && u!=n) deg2.insert(u);
    if(deg[v] == 2 && v!=1 && v!=n) deg2.insert(v);
}

int getOneEdge(int u, int v){
    if(u>v) swap(u, v);

    if(circuitCnt.find({u, v}) != circuitCnt.end() && circuitCnt[{u, v}] == 2){
        multi.erase({u, v});
    }

    auto it = circuitMap.find({u, v});
    int res = it->second;
    circuitMap.erase(it);
    circuitCnt[{u, v}]--;
    if(circuitCnt[{u, v}] == 0){
        circuitCnt.erase({u, v});
    }

    if(deg[u] == 2 && u!=1 && u!=n) deg2.erase(u);
    if(deg[v] == 2 && v!=1 && v!=n) deg2.erase(v);
    adj.erase(adj.find({u, v}));
    adj.erase(adj.find({v, u}));
    deg[u]--, deg[v]--;
    if(deg[u] == 2 && u!=1 && u!=n) deg2.insert(u);
    if(deg[v] == 2 && v!=1 && v!=n) deg2.insert(v);
    totalCnt--;
    return res;
}

void resolveMulti(int u, int v){
    int c1 = getOneEdge(u, v), c2 = getOneEdge(u, v);
    int nc = newCircuit(2, c1, c2);
    insertEdge(u, v, nc);
}

void resolveDeg2(int x){
    int a = -1, b = -1;
    {
        auto it = adj.lower_bound({x, -1});
        a = it->second;
        ++it;
        b = it->second;
    }

    int c1 = getOneEdge(x, a);
    int c2 = getOneEdge(x, b);
    int nc = newCircuit(1, c1, c2);
    insertEdge(a, b, nc);
}

void encodeCircuit(){
    for(int i=1; i<=n; i++){
        for(int j: edge[i]){
            if(i>j) continue;
            insertEdge(i, j);
        }
    }

    while(totalCnt > 1){
        if(!multi.empty()){
            pair<int, int> tmp = *multi.begin();
            resolveMulti(tmp.first, tmp.second);
        }
        else if(!deg2.empty()){
            int x = *deg2.begin();
            resolveDeg2(x);
        }
        else{
            cout << "Something went wrong";
            exit(1);
        }
    }

    root = circuitMap.begin()->second;
}

void solve(){
    cout << solveCircuit(root).first;
}

'시리즈 > 과거 청산' 카테고리의 다른 글

과거 청산 챌린지 #6  (0) 2026.02.09
과거 청산 챌린지 #5  (0) 2026.01.28
과거 청산 챌린지 #4  (0) 2026.01.24
과거 청산 챌린지 #3  (0) 2026.01.17
과거 청산 챌린지 #1  (0) 2026.01.11
공지사항
최근에 올라온 글
Total
Today
Yesterday