NOIP 2023 알고리즘 문제 풀이

문제 1: 간단한 문자열 처리 첫 번째 문제는 매우 straightforward합니다. 각 행의 문자를 추출하여 정렬한 후 최소 문자열을 만들고, 역순으로 배치하여 최대 문자열을 만들면 됩니다.

코드 확인하기

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
template<typename T>
void processRange(T* start, T* end, function<void(T*)> op) {
    for (T* p = start; p <= end; ++p) op(p);
}

int rowCount, colCount;
const int MAX = 3005;
char data[MAX][MAX];
char minStr[MAX][MAX];
char maxStr[MAX][MAX];
char buffer[MAX];
int elementCount;

bool compareSequences(char first[], char second[]) {
    for (int i = 1; i <= colCount; ++i) {
        if (first[i] < second[i]) return true;
        if (first[i] > second[i]) return false;
    }
    return true;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> rowCount >> colCount;
    
    for (int i = 1; i <= rowCount; ++i) {
        cin >> (data[i] + 1);
    }
    
    for (int i = 1; i <= rowCount; ++i) {
        elementCount = 0;
        for (int j = 1; j <= colCount; ++j) {
            buffer[++elementCount] = data[i][j];
        }
        
        sort(buffer + 1, buffer + elementCount + 1);
        for (int j = 1; j <= elementCount; ++j) {
            minStr[i][j] = buffer[j];
        }
        
        reverse(buffer + 1, buffer + elementCount + 1);
        for (int j = 1; j <= elementCount; ++j) {
            maxStr[i][j] = buffer[j];
        }
    }
    
    for (int i = 1; i <= rowCount; ++i) {
        bool hasConflict = false;
        for (int j = 1; j <= rowCount; ++j) {
            if (j == i) continue;
            if (compareSequences(maxStr[j], minStr[i])) {
                hasConflict = true;
                cout << 0;
                break;
            }
        }
        if (!hasConflict) cout << 1;
    }
    
    return 0;
}

문제 2: 변수 상태 추적 이 문제는 변수에最后一次 할당된 값을 추적하는 것으로, 여러 상황을 나누어讨论해야 합니다.

Case 1: 특정 변수에 대한 이전 연산이 없는 경우

  • 奇数번否定 연산이 수행됨 → U 상태
  • 그렇지 않으면 U가 아님

Case 2: 이전에 v 클래스 할당이 존재하는 경우

  • 否定 후元の値와 다름 → U 상태
  • 그렇지 않으면 U가 아님

Case 3: 이전에 x_j 클래스 할당이 존재하는 경우 그래프를 구축합니다. x_j → x_i 방향의 간선을 만들고, 마지막 할당은 하나뿐이므로 각 노드는 하나의 부모만 가집니다. 최종적으로 트리가 형성되거나 순환이 발생할 수 있습니다. 간선 가중치는 할당 후 논리NOT 연산 횟수의奇偶性을 사용하고, 경로상의 가중치를 XOR로 계산합니다.

Case 3에서 U가 발생하는 조건은 다음과 같습니다: 실험 결과, 순환에 홀수 개의 1이 참여하면 순환상의 각 노드가 한 바퀴 돌고 나서 자신의 반대인 결론을導出합니다. 이 경우 순환상의 모든 노드가 U여야 하며, U에서導出된 자식 노드들도 U가 됩니다 (연산 특성에 의해 명확함).

훌륭한 대체 구현으로는 Union-Find를 사용하는 방법이 있습니다.

코드 확인하기

#include <bits/stdc++.h>
using namespace std;

using int64 = long long;
const int MAXN = 100005;
const int TRUE_VAL = 100001;
const int FALSE_VAL = -100001;
const int UNKNOWN = 0;

int nodeCount, operationCount;
int parent[MAXN];
bool visited[3 * MAXN];

int findRoot(int x) {
    int result;
    
    if (x == TRUE_VAL || x == FALSE_VAL) {
        result = x;
    }
    else if (visited[nodeCount - x] || x == UNKNOWN) {
        result = UNKNOWN;
    }
    else if (visited[x + nodeCount]) {
        result = TRUE_VAL;
    }
    else if (x < 0) {
        if (x == -parent[-x]) {
            result = x;
        }
        else {
            visited[x + nodeCount] = true;
            result = findRoot(-parent[-x]);
            visited[x + nodeCount] = false;
        }
    }
    else {
        if (x == parent[x]) {
            result = x;
        }
        else {
            visited[x + nodeCount] = true;
            parent[x] = findRoot(parent[x]);
            result = parent[x];
            visited[x + nodeCount] = false;
        }
    }
    
    return result;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int testCase, queryCount;
    cin >> testCase >> queryCount;
    
    while (queryCount--) {
        cin >> nodeCount >> operationCount;
        
        for (int i = 1; i <= nodeCount; ++i) {
            parent[i] = i;
        }
        
        for (int i = 1; i <= operationCount; ++i) {
            char operation;
            int a, b;
            
            cin >> operation;
            
            if (operation == 'T') {
                cin >> a;
                parent[a] = TRUE_VAL;
            }
            else if (operation == 'F') {
                cin >> a;
                parent[a] = FALSE_VAL;
            }
            else if (operation == 'U') {
                cin >> a;
                parent[a] = UNKNOWN;
            }
            else {
                cin >> a >> b;
                if (operation == '+') {
                    parent[a] = parent[b];
                }
                else {
                    parent[a] = -parent[b];
                }
            }
        }
        
        int answer = 0;
        for (int i = 1; i <= nodeCount; ++i) {
            if (findRoot(i) == UNKNOWN) {
                answer++;
            }
        }
        
        cout << answer << "\n";
    }
    
    return 0;
}

문제 3: 주의력이 필요한 구현 세 번째 문제는 상당한 집중력이 필요합니다. 작성자는 O(T × n × m) 복잡도의 솔루션만 구현할 수 있었습니다.

태그: algorithm competitive-programming C++ sorting graph-theory

6월 29일 21:06에 게시됨