Cut Vertex

native

각 정점 삭제후, 컴코넌트 개수 확인 dfs(v) 번 수행 visited에 false인(방문하지 않은 vertex가 있는 지) 확인, 있다면 cut vertex가 존재

###

각 정점을 루트로 하는 서브트리에서 역방향 간선을 통해 닿는 정점들의 최소 발견순서 반환하면된다 발견순서를 저장하기 위해 discovered[] 두고, visted/found 는 필요없다. 무향이고 (교차간선 X), 발견순서로 모든 간선 구분가능

시작 정점의 서브트리에서 역방향으로 갈 수 있는 정점 (갈 수 있는 가장 높은번호) 중 시작 정점의 발견순서보다 작은 것(상위 조상)이 없다면 단절점이다.

// start를 root로 하는 서브트리에 있는 단절점을 찾는다
// 반환값은 해당 서브트리의 역방향 간선으로 갈 수 있는 정점 중
// 가장 일찍 발견된 정점의 발견 시점
private static int dfs(int start, boolean root) {
    discovered[start] = ++depth;

    // 자식이 없다면 현재 점 반환
    int dfn = discovered[start];
    int dgree = 0;

    for (Integer next : path.get(start)) {
        if (discovered[next] == 0) {
            // root인 경우 단절점 판단을 자손 개수체크
            ++dgree;
            // 이 서브 트리에서 갈 수 있는 가장 높은 정점의 번호
            int low = dfs(next, false);
            // 번호가 자신 이하라면 단절점
            if (root == false && low >= discovered[start]) {
                solution[start] = true;
            }
            // root값으로 맞춰준다
            dfn = Math.min(dfn, low);
        } else {
            // 현재 횟수와 방문했던 위치와 비교.
            dfn = Math.min(dfn, discovered[next]);
        }
    }
    if (root) {
        if (dgree > 1) {
            solution[start] = true;
        }
    }
    return dfn;
}
pow using device and conquer

x^n

x^8 = x^4 * x^4 x^4 = x^2 * x^2 x^2 = x * x

if x is even x^n = x^n/2 * x^n/2 else x^n = x^(n-1)/2 * x^(n-1)/2 * x

가짜 소수: https://www.acmicpc.net/problem/4233

pow using devide and conquer

ll mod_pow(ll x, ll n, ll mod){
    ll res = 1; 
    while (n){
        if (n&1) res = res*x%mod; // if n is odd 
        x = x*x%mod;  
        n >>= 1;  // same as n /= 2;  
    }
    return res;  
}
 

소수판별

bool isPrime(int n){
    for (int i = 2; i*i <= n; i++){
        if (n%i == 0) 
            return false; 
    }
    return n != 1;  
}
Binary Search

정렬된 리스트에 어떤 수가 있는지 없는지를 조사한다.

주로, 정답을 구하기는 어려운데 정답을 검증하기 쉬운 경우의 최적의 해를 찾는다. 최대값의 최소화, 최소값의 초대화 등의 경우가 많다.

경비행기

https://www.acmicpc.net/problem/2585

최소 연료통이 얼마인가? 연료통 x 로 갈 수 있는 거리내 비행장을 찾아보고, 비행장이 나오면 급유하여 다시 갈 수 있는 거리내 비행장을 찾아본다. 목적지가 나올 때까지 반복한다. k번 이하내로 목적지를 못가면 x는 해가 아니다.

// mid 가 해(x)보다 크거나,작거나 같은경우
while (left <= right) {
    mid = (left + right) / 2;
    if (mid > ) {
        right = mid - 1;
    else if (mid < ) {
        left = mid + 1;
    } else {
        해를 찾음!
        break
    }
}
// mid 가 해이거나 아니거나
// mid 가 해의 후보군일경우 또다른 해를 찾는다
// left == right 일경우 loop 에서 빠져나올 수 없을 수 있음으로 주의!

while (left < right) {
    mid = (left + right) / 2;
    if (f(mid) == 1) {
        // 해를 찾음
        right = mid;
    } else {
        left = mid + 1;
    }
}
System.out.println(right);
// 구현에 따라 다를 수 있다
int ans = 0;
while (left <= right) {
    mid = (left + right) / 2;
    if (f(mid) == 1) {
        ans = mid;
        right = mid-1;
    } else {
        left = mid + 1;
    }
}
System.out.println(ans);
BFS

BFS

  • 최단 경로 전략
  • 복잡도: O(V + E) 모든 정점을 한번씩 방문하며 정점을 방문할 때마다 인접한 모든 간선 검사

자동차 경주

https://www.acmicpc.net/problem/2611

다익스트라에서 큐의 우선순위를 “가중치가 높은 노드”로 바꾸면 된다. 경로 출력은 큐에 넣는 작업을 할때마다 그 때의 이전 노드를 따로 저장 (즉, 어디서부터 왔는 가를 저장)하여 마지막엔 시작 노드로부터 그 역순으로 따라올라가면 경로를 얻을 수 있다.

for (V next : adj.get(curr.to)) {
    if (dist[curr.to] + next.w > dist[next.to]) {
        dist[next.to] = dist[curr.to] + next.w;
        q.add(new V(next.to, dist[next.to]));
        trace[next.to] = curr.to;
    }
}

//경로 출력은 stack을 쓰는게 깔끔
System.out.println(dist[1]);

// LIFO
Stack<Integer> st = new Stack<Integer>();
st.push(1);
st.push(trace[1]);
int last = trace[1];
while (last != 1) {
    last = trace[last];
    st.push(last);
}
while (!st.isEmpty()) {
    System.out.print(st.pop() + " ");
}
// 혹은 재귀를 이용
static void printPath(int last) {
    if (trace[last] == 1) {
        System.out.print(1 + " " + last);
        return;
    }
    printPath(trace[last]);
    System.out.print(" "+ last);
}

native dfs static void dfs(int curr, ArrayList path, int max) { path.add(curr); if (path.size() > 1 && curr == 1) { if (max > ans) { ans = max; max_path = (ArrayList) path.clone(); } path.remove(path.size() - 1);

    return;
}
for (V next : adj.get(curr)) {
    dfs(next.to, path, max + next.w);
    // path.remove(path.size()-1); 여기서 remove할 경우 최종적으로 curr 에 대한 remove 도 필요하다
}
// context 가 끝난 시점에 remove하는게 간결
path.remove(path.size() - 1); }

Dijkstra

한 정점에서 모든 정점의 최단거리 방향그래프

무향 BFS 최단거리와 비슷하나 priority queue를 이용

PriorityQueue<Edge> que = new PriorityQueue<>();
visited[start] = true;
dist[start] = 0; // INF로 초기화
que.add(new Edge(start, 0));
 
while (!que.isEmpty()) {
    int cur_index = que.peek().index;
    int cur_w = que.peek().weight;

    que.poll();

    if (dist[cur_index] < cur_w) {
        continue;
    }
    for (Edge edg: list.get(cur_index)) {
        int next_index = edg.index;
        int next_weight = edg.weight;
    
        if (next_index == cur_index) {
            continue;
        }
        if (!visited[next_index] || dist[cur_index] + next_weight < dist[next_index]) {
            dist[next_index] = dist[cur_index] + next_weight;
            visited[next_index] = true;
            que.add(new Edge(next_index, dist[next_index]));
        }
    }
}
for (int i = 1; i <= V; i++) {
    if (dist[i] == Integer.MAX_VALUE) {
        System.out.println("INF");
    } else {
        System.out.println(dist[i]);
    }
}

9-퍼즐

  • 복잡도 정점의 수: 9! 탐색 분기수 b: 4방향이나 이전정점돌아가는길이나 중복경우 존재해 약 3이하의 어느값 최단거리를 d 라고 한다면 O(b^d)

b=2, d = 20 이라하면 2^20 = 최소 백만 2^10 = 1024 2^20 = 100만 2^32 = 42억

양방향 탐색이면 O(b^(d/2)) 복잡도가 나온다

  • 퍼즐의 상태를 어떻게 나타내나? – 1. vector를 이용해 각 위치의 순서대로 값을 넣어둔다 – 2. bit? 각각의 상태를 bit로 표현한다. 최대 크기가 8 (1000) 이므로 각 상태는 bit 4개필요, 총 9개이므로 36bit (4 * 9) 필요하므로 64bit 정수형으로 표현이 가능하다.
Edmonds-Karp algorithm for maximum flow problem

Flow networks A flow network G = (V,E), directed graph where each edge (u,v) E is associated with a nonnegative capacity c(u, v) >= 0. There are two vertices in a flow network: a source s, and a sink t.

f(u, v) ≤ c(u, v) Σf(k, u) = Σf(u, l) equal in and out flow Σf(S, k) = Σf(k, T)

f(u, v) = -f(v, u)

  1. s 에서 t 로가는 증가경로 (augmenting path) 하나 선택. 여유 용량 (regidual) c(u, v) - f(u,v) > 0
  2. 경로 중 취소 간선 찾는다. F로 표현
  3. 모든 간선에 대해 F만큼 유량 추가한다. 즉 F만큼 유량을 흘려보낸다. f(u,v) += F. 이때 역방향으로 F 만큼 흘려준다. f(v,u) -= F. For each edge u → v on the path:
  • Decrease c(u → v) by f
  • Increase c(v → u) by f
  1. 1부터 증가 경로가 없을 때까지 다시 반복한다.

역방향으로 음의 량을 보내는건 어떤의미인가?.

O(VE^2)

#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int MAX_V = 52;
const int INF = 1000000000;
 
// 정점 문자를 0~51 사이의 번호로 바꿔주는 간단한 함수
inline int CtoI(char c){
    if(c <= 'Z') return c - 'A';
    return c - 'a' + 26;
}
 
int main(){
    int N; // 간선 개수
    int c[MAX_V][MAX_V] = {0}; // c[i][j]: i에서 j로 가는 간선의 용량
    int f[MAX_V][MAX_V] = {0}; // f[i][j]: i에서 j로 현재 흐르는 유량
    vector<int> adj[MAX_V]; // 인접 리스트
 
    // 간선 정보 입력받기
    scanf("%d", &N);
    for(int i=0; i<N; i++){
        char u, v;
        int w;
        scanf(" %c %c %d", &u, &v, &w);
        u = CtoI(u); v = CtoI(v);
        c[u][v] += w; // 같은 간선이 여러 번 들어올 수 있으므로 +=
        adj[u].push_back(v);
        adj[v].push_back(u); // 역방향 간선도 추가해줘야 함
    }
 
    // total: 총 유량, S: 소스, E: 싱크
    int total = 0, S = CtoI('A'), E = CtoI('Z');
    // 증가 경로를 못 찾을 때까지 루프
    while(1){
        // 증가 경로를 BFS로 찾음
        int prev[MAX_V];
        fill(prev, prev+MAX_V, -1);
        queue<int> Q;
        Q.push(S);
        while(!Q.empty()){
            int curr = Q.front();
            Q.pop();
            for(int next: adj[curr]){
                // c[i][j]-f[i][j] > 0: i에서 j로 유량을 흘릴 여유가 남았는가?
                // prev[next] == -1: next 정점을 아직 방문하지 않았는가?
                if(c[curr][next]-f[curr][next] > 0 && prev[next] == -1){
                    Q.push(next);
                    prev[next] = curr; // 경로를 기억하기 위해 prev 배열 사용
                    if(next == E) break; // 싱크에 도달하면 나옴
                }
            }
        }
        // 싱크로 가는 경로가 더 없으면 루프 탈출
        if(prev[E] == -1) break;
 
        // 경로상에서 가장 허용치가 낮은 곳을 찾음
        int flow = INF;
        for(int i=E; i!=S; i=prev[i])
            flow = min(flow, c[prev[i]][i]-f[prev[i]][i]);
        // 찾은 flow만큼 해당 경로에 유량 흘려줌
        for(int i=E; i!=S; i=prev[i]){
            f[prev[i]][i] += flow;
            f[i][prev[i]] -= flow;
        }
        // 총 유량 값 증가
        total += flow;
    }
    // 결과 출력
    printf("%d\n", total);
}

References: http://www.geeksforgeeks.org/ford-fulkerson-algorithm-for-maximum-flow-problem/