레이블이 lowest common ancestor인 게시물을 표시합니다. 모든 게시물 표시
레이블이 lowest common ancestor인 게시물을 표시합니다. 모든 게시물 표시

2017년 7월 22일 토요일

10637 Minimum Spanning Tree

10637 Minimum Spanning Tree https://www.acmicpc.net/problem/10637

1626 두 번째로 작은 스패닝 트리 문제와 유사하다.
https://lyzqm.blogspot.kr/2017/07/1626.html

그래프 $G$가 $m_{i}$의 간선들로 이루어져있을 때 각각의 $m_{i}$에 대해서 해당 간선이 존재하지 않을때 
MST의 값을 구하는 문제이다.

처음에 떠오른 생각은 우선 MST를 만든후 $m_{i}$가 트리 간선인 경우 해당 간선을 지우고 
트리간선이 아닌 다른간선으로 대체하는 방안을 떠올렸다.
($m_{i}$가 트리간선이 아닌경우는 MST값이 나올것이다.)
트리간선이 제거되는 경우 위 그림과 같이 두개의 component로 분리된다.
component를 연결하는 최소의 값을 지닌 회색 간선이 트리간선을 대체할 수 있을 것이다.
(검정색 - 트리간선, 회색 - 트리간선이 아닌 간선(일반간선으로 칭함))

문제는 어떻게 하느냐 인데 1626문제에서 처럼 LCA를 이용한다.
대신 일반 간선의 입장에서 생각해야한다.
이 간선은 {$u,v$}정점을 가리킬때 $u$에서 $v$로 가는 모든 MST 간선을 대체 할 수 있게 된다.

해당 일반간선이 MST간선을 대체할 수 있더라도 더 작은 일반 간선에 의해 대체 가능할 수 도 
있기 때문에 작은 일반간선부터 대체 가능한 MST 간선을 정해놓아야 한다.

따라서 $m_{i}$가 {$u,v$}일 때 {$u, LCA$}, {$LCA, v$}의 아직 대체 되지 않은 MST간선을 구해서 갱신해주면 된다.

하지만 이 갱신작업을 depth를 한칸 한칸씩 내리며 찾는다면 TLE가 나온다.
따라서 중복되지 않도록 최적화를 시켜줘야 하는데 다음과 같은 방법을 사용하였다.
위와 같이 붉은 선은 이미 갱신되었고 $u$에서 $LCA$로 이동한다고 한다면 
이미 갱신된 트리간선은 갈 필요가 없다.
그러므로 갱신될때마다 경로를  $LCA$로 변경시켜 주면 된다.
(추가적인 갱신조건은 코드를 참고)

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int N, M;
int par[100001], rrank[100001], depth[100001];
int pr[100001];
int edge[200001];
ll ans[200001];
int pprev[100001][21];
bool isMSTedge[200001];
pair<ll, pair<intpair<intint>>> adj[200001];    //(d, index, u, v)
ll adj_copy[200001];
vector<pair<ll, pair<intint>>> adj_mst[100001];        //u - (d, index, v)
pair<ll, pair<int,int>> adj_n_mst[200001];        //MST아닌 간선 (d, u, v)
void init() {
    memset(depth, -1sizeof depth);
    for (int n = 0;n < M;n++) ans[n] = -1;
    for (int n = 1;n <= N;n++
        par[n] = n, rrank[n] = 0;
}
int find(int x) {
    if (x == par[x]) return x;
    return par[x] = find(par[x]);
}
bool merge(int u, int v) {
    u = find(u), v = find(v);
    if (u == v) return false;
    if (rrank[u] > rrank[v]) swap(u, v);
    else if (rrank[u] == rrank[v]) rrank[v]++;
    par[u] = v;
    return true;
}
void dfs(int here) {
    for (auto n : adj_mst[here]) {
        int next = n.second.second;
        if (depth[next] == -1) {
            int idx = n.second.first;
            depth[next] = depth[here] + 1;
            pprev[next][0= here;
            pr[next] = here;
            edge[next] = idx;
            dfs(next);
        }
    }
}
void preprocess() {
    for (int j = 1;j < 21;j++) {
        for (int i = 1;i <= N;i++) {
            pprev[i][j] = pprev[pprev[i][j - 1]][j - 1];
        }
    }
}
int getLCA(int u, int v) {
    if (depth[u] < depth[v]) swap(u, v);
    for (int i = 20;i >= 0;i--) {
        if (depth[u] - depth[v] >= (1 << i))
            u = pprev[u][i];
    }
    if (u == v) return u;
    for (int i = 20;i >= 0;i--) {
        if (pprev[u][i] != pprev[v][i])
            u = pprev[u][i], v = pprev[v][i];
    }
    return pprev[u][0];
}
int main() {
    scanf("%d%d"&N, &M);
    init();
    for (int m = 0;m < M;m++) {
        int u, v, d;
        scanf("%d%d%d"&u, &v, &d);
        adj[m] = { (ll)d,{m,{u,v} } };
        adj_copy[m] = (ll)d;
    }
    sort(adj, adj + M);
    ll mst_val = 0;
    int mst_cnt = 0;    //
    int n_mst_cnt = 0;
    for (int m = 0;m < M;m++) {
        ll d = adj[m].first;
        int u = adj[m].second.second.first, v = adj[m].second.second.second;
        if (merge(u, v) && mst_cnt < N - 1) {        //서로다른 집합인 경우 - MST
            int idx = adj[m].second.first;
            isMSTedge[idx] = true;
            adj_mst[u].push_back({ d, { idx, v } });
            adj_mst[v].push_back({ d, { idx, u } });
            mst_val += d;
            ++mst_cnt;
        }
        else {        //MST아닌 간선
            adj_n_mst[n_mst_cnt] = { d,{u,v} }, ++n_mst_cnt;
        }
    }
    if (mst_cnt != N - 1) {
        for (int m = 0;m < M;m++printf("-1\n");
        return 0;
    }
    depth[1= 0;
    dfs(1);
    preprocess();        //2^K의 자식노드 전처리
    int cnt = 0;
    sort(adj_n_mst, adj_n_mst + n_mst_cnt);
    for (int m = 0;m < n_mst_cnt;m++) {        //MST 간선이 아닌 간선들
        ll d = adj_n_mst[m].first;
        int u = adj_n_mst[m].second.first, v = adj_n_mst[m].second.second;
        int lca = getLCA(u, v);
        if (lca != u) {
            while (1) {        // path(u, lca)
                int idx = edge[u];
                if (ans[idx] == -1) {
                    cnt++;
                    ans[idx] = mst_val + d - adj_copy[idx];
                }
                int tmp_u = u;
                u = pr[u];
                if (depth[lca] < pr[tmp_u])
                    pr[tmp_u] = lca;
                if (depth[lca] >= depth[u]) break;
            }
        }
        if (lca != v) {
            while (1) {        // path(v, lca)
                int idx = edge[v];
                if (ans[idx] == -1) {
                    cnt++;
                    ans[idx] = mst_val + d - adj_copy[idx];
                }
                int tmp_v = v;
                v = pr[v];
                if (depth[lca] < pr[tmp_v])
                    pr[tmp_v] = lca;
                if (depth[lca] >= depth[v]) break;
            }
        }
        if (cnt == mst_cnt) break;
    }
    for (int m = 0;m < M;m++) {
        if (!isMSTedge[m]) printf("%lld\n", mst_val);        //mst가 아닌 간선 - 변함없음
        else printf("%lld\n", ans[m]);
    }
    return 0;
}
cs

2017년 7월 14일 금요일

1626 두 번째로 작은 스패닝 트리

1626 두 번째로 작은 스패닝 트리 https://www.acmicpc.net/problem/1626

최소 스패닝 트리 보다 다음 으로 작은 스패닝 트리를 찾는 문제이다.

MST를 구해주고 MST를 구성하지 않는 간선을 가지고 계산한다.
그 간선의 정보가 {$d$, $u$,$v$}일때 $u$,$v$ 정점의 LCA까지 최대 간선길이를 구한다.
(LCA에서 par저장하듯이 최대 간선길이도 저장하면 됨)

예외가 있는데 만약 그 간선길이가 $d$와 같다면 두 번째 스패닝 트리가 없는걸로 판단할 수 있기 때문에

LCA에서 depth를 2배씩 늘려가며 올라가듯 $d$보다 작지만 가장 큰 간선을 찾으면 된다.

참고로 merge연산할 때 find()이후의 정점을 가지고 다른 정보를 저장하면 당연히 안된다.
(개뻘짓함..)


#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<intpair<intint>> P;
#define mp(a,b) make_pair(a,b)
#define mt(a,b,c) mp(a,mp(b,c))
struct Edge {
    int d, u, v;
    Edge() : Edge(-1-10) {}
    Edge(int dd, int uu, int vv) : u(uu), v(vv), d(dd) {}
    bool operator <(const Edge& O)const { return d < O.d; }
};
int N, M;
Edge adj[200001];
bool isMST[200001];
int group[50001], trank[50001];
vector<pair<intint>> MST[50001];
int depth[50001], par[50001][21], cost[50001][21];
void init() {
    for (int n = 1;n <= N;n++) {
        group[n] = n;
        depth[n] = -1;
        for (int m = 0;m < 21;m++)
            par[n][m] = -1;
    }
    for (int m = 0;m < M;m++)
        isMST[m] = false;
}
int find(int x) {
    if (x == group[x]) return x;
    return group[x] = find(group[x]);
}
void merge(int a, int b) {
    if (trank[a] > trank[b]) swap(a, b);
    else if (trank[a] == trank[b]) trank[b]++;
    group[a] = b;
}
void dfs(int here) {
    for (auto n : MST[here]) {
        int next = n.second;
        if (depth[next] == -1) {
            depth[next] = depth[here] + 1;
            par[next][0= here;
            cost[next][0= n.first;    
            dfs(next);
        }
    }
}
int second_max(int u, int i, int d) {
    if (i == 0return 0;
    int ret = 0;
    if (cost[u][i - 1== d) 
        ret = max(ret, second_max(u, i - 1, d));
    else ret = max(ret, cost[u][i - 1]);
    if (cost[par[u][i - 1]][i - 1== d)
        ret = max(ret, second_max(par[u][i - 1], i - 1, d));
    else ret = max(ret, cost[par[u][i - 1]][i - 1]);
    return ret;
}
int LCA_max(int u, int v, int d) {
    if (depth[u] > depth[v]) swap(u, v);
    int dmax = 0;
    if (depth[u] != depth[v]) {
        for (int i = 20;i >= 0;i--) {
            if (depth[v] - depth[u] >= (1 << i)) {
                if (cost[v][i] == d) dmax = max(dmax, second_max(v, i, d));
                else dmax = max(dmax, cost[v][i]);
                v = par[v][i];
            }
        }
    }
    if (u == v) return dmax;
    for (int i = 20;i >= 0;i--) {
        if (par[u][i] != -1 && par[u][i] != par[v][i]) {
            if (cost[v][i] == d) dmax = max(dmax, second_max(v, i, d));
            else dmax = max(dmax, cost[v][i]);
            if (cost[u][i] == d) dmax = max(dmax, second_max(u, i, d));
            else dmax = max(dmax, cost[u][i]);
            u = par[u][i];
            v = par[v][i];
        }
    }
    if (cost[u][0< d) dmax = max(dmax, cost[u][0]);
    if (cost[v][0< d) dmax = max(dmax, cost[v][0]);
    return dmax;
}
int main() {
    scanf("%d%d"&N, &M);
    init();
    for (int m = 0;m < M;m++) {
        int u, v, d;
        scanf("%d%d%d"&u, &v, &d);
        adj[m] = Edge(d, u, v);
    }
    sort(adj, adj + M);
    bool flag = false;
    int mst1 = 0, cnt = 0;
    for (int m = 0;m < M;m++) {
        int d = adj[m].d,
            u = adj[m].u,
            v = adj[m].v;
        u = find(u), v = find(v);
        if (u == v) continue;
        if (u != v){        //MST 간선 가능
            merge(u, v);
            isMST[m] = true;
            //find이후의 u,v를 넣으면 안된다!!!! (u,v)가 변경되있는 상태
            MST[adj[m].u].push_back({ d,adj[m].v });
            MST[adj[m].v].push_back({ d,adj[m].u });
            cnt++;
            mst1 += d;
        }
        if (cnt == N - 1) {
            flag = true;
            break;
        }
    }
    if (!flag) {        //MST가 없을 때
        printf("-1\n");
        return 0;
    }
    depth[1= 0;
    par[1][0= 0;
    cost[1][0= 0;
    dfs(1);            
    //부모노드, 간선길이 전처리
    for (int m = 1;m < 21;m++) {
        for (int n = 1;n <= N;n++) {
            par[n][m] = par[par[n][m - 1]][m - 1];
            cost[n][m] = max(cost[par[n][m - 1]][m - 1], cost[n][m - 1]);
        }
    }
    long long ans = (long long )3e13;
    for (int m = 0;m < M;m++) {
        if (isMST[m]) continue;
        //MST간선이 아닌것들에서 골라준다.
        int d = adj[m].d,
            u = adj[m].u,
            v = adj[m].v;
        int get_MAX_d = LCA_max(u, v, d);        //d보단 작지만 가장 큰 간선
        ans = min(ans, (long long)(mst1 + d - get_MAX_d));        //d를 넣고 MST중 가장 큰 간선을 뺀다.
    }
    if (ans == mst1 || ans == (long long)3e13)printf("-1\n");
    else printf("%lld\n", ans);
    return 0;
}
cs

2017년 6월 27일 화요일

(LCA - Lowest Common Ancestor) 1761 정점들의 거리

백준 1761 정점들의 거리 https://www.acmicpc.net/problem/1761
3176 도로 네트워크 https://www.acmicpc.net/problem/3176
Codeforces #425 (Div. 2) D - Misha, Grisha and Underground  
http://codeforces.com/contest/832/problem/D

LCA (최소 공통 조상) 
어떤 두 정점의 가장 가까운 공통 루트가 누구인지 알아내는 문제이다.
종만북에는 세그먼트 트리로 LCA를 구하지만 넘 복잡하고 보통 DP를 이용해서 구한다.

DFS로 노드들의 depth를 구한 후 $2^K$위의 루트 노드가 무엇인지 DP를 이용하여 전처리한다.
여기서 정점사이의 거리나 최소거리 등의 정보도 함께 넣을 수 있다.

1. 정점들의 거리
트리에서 두 정점 사이의 거리를 구하는 문제이다. 
전처리 할 때 현재 노드에서 $2^K$위의 루트 노드 까지 거리도 전처리 해준다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define INF 987654321
int N, M;
vector<pair<intint>> adj[40001];
bool visit[40001];
int depth[40001], par[40001][17], dist[40001][17];
void dfs(int here, int d){
    visit[here] = true;
    depth[here] = d;
    for (auto n : adj[here]){
        int next = n.second;
        int cost = n.first;
        if (!visit[next]){
            par[next][0= here;
            dist[next][0= cost;
            dfs(next, d + 1);
        }
    }
}
void dp(){
    for (int m = 1; m < 17; m++){
        for (int n = 1; n <= N; n++){
            par[n][m] = par[par[n][m - 1]][m - 1];
            dist[n][m] = dist[n][m - 1+ dist[par[n][m - 1]][m - 1];
        }
    }
}
int lca(int u, int v){
    int ret = 0;
    if (depth[u] < depth[v]) swap(u, v);
    for (int i = 16; i >= 0; i--){
        if (depth[u] - depth[v] >= (1 << i)){
            ret += dist[u][i];
            u = par[u][i];
        }
    }
    //depth같아짐
    if (u == v) return ret;
    for (int i = 16; i >= 0; i--){
        if (par[u][i] != par[v][i]){
            ret += (dist[u][i] + dist[v][i]);
            u = par[u][i], v = par[v][i];
        }
    }
    ret += (dist[u][0+ dist[v][0]);
    return ret;
}
int main(){
    scanf("%d"&N);
    for (int n = 0; n < N - 1; n++){
        int u, v, d;
        scanf("%d%d%d"&u, &v, &d);
        adj[u].push_back({ d, v });
        adj[v].push_back({ d, u });
    }
    //전처리
    dfs(10);
    dp();
    scanf("%d"&M);
    while (M--){
        int a, b;
        scanf("%d%d"&a, &b);
        printf("%d\n", lca(a, b));
    }
    return 0;
}
cs

2. 도로 네트워크
문제를 잘 읽어보면 네트워크는 트리형태이다.
이 문제에서는 최소 간선, 최대 간선에 대해 전처리 해주면 된다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define INF 987654321
int N, K;
vector<pair<intint>> adj[100001];
bool visit[100001];
int depth[100001];
int par[100001][21];
int edge_min[100001][21], edge_max[100001][21];
void dfs(int here, int d){
    visit[here] = true;
    depth[here] = d;
    for (auto n : adj[here]){
        int next = n.second;
        int cost = n.first;
        if (!visit[next]){
            par[next][0= here;
            edge_min[next][0= cost;
            edge_max[next][0= cost;
            dfs(next, d + 1);
        }
    }
}
void dp(){
    for (int n = 1; n < 21; n++){
        for (int m = 1; m <= N; m++){
            par[m][n] = par[par[m][n - 1]][n - 1];
            edge_min[m][n] = min(edge_min[m][n - 1], edge_min[par[m][n - 1]][n - 1]);
            edge_max[m][n] = max(edge_max[m][n - 1], edge_max[par[m][n - 1]][n - 1]);
        }
    }
}
pair<int,int> lca(int a, int b){
    int rmin = INF, rmax = -INF;
    if (depth[a] < depth[b]) swap(a, b);
    for (int i = 20; i >= 0; i--){
        if (depth[a] - depth[b] >= (1 << i)){
            rmin = min(rmin, edge_min[a][i]);
            rmax = max(rmax, edge_max[a][i]);
            a = par[a][i];
        }
    }
    if (a == b) return { rmin, rmax };
    for (int i = 20; i >= 0; i--){
        if (par[a][i] != par[b][i]){
            rmin = min(rmin, min(edge_min[a][i], edge_min[b][i]));
            rmax = max(rmax, max(edge_max[a][i], edge_max[b][i]));
            a = par[a][i];
            b = par[b][i];
        }
    }
    rmin = min(rmin, min(edge_min[a][0], edge_min[b][0]));
    rmax = max(rmax, max(edge_max[a][0], edge_max[b][0]));
    return { rmin, rmax };
}
int main(){
    scanf("%d"&N);
    for (int n = 0; n < N - 1; n++){
        int u, v, d;
        scanf("%d%d%d"&u, &v, &d);
        adj[u].push_back({ d, v });
        adj[v].push_back({ d, u });
    }
    //전처리
    dfs(10);
    dp();
    scanf("%d"&K);
    for (int k = 0; k < K; k++){
        int D, E;
        scanf("%d%d"&D, &E);
        pair<int,int> ans = lca(D, E);
        printf("%d %d\n", ans.first, ans.second);
    }
    return 0;
}
cs

3. Codeforces #425 (Div. 2) - D
나의 첫 codeforces 대회였다. 
2시간이라는 촉박한 시간과 영어여서 문제이해하는데도 한참 걸렸다.
(그리고 Div. 2 치고 어렵게 나온거같다;; 1문제 품)

B는 문자열, C는 수식과 실수가 나와서 바로넘어갔었다.
D를 실제로 C보다 더 많이 풀었는데 대회때는 못풀었지만 그나마 풀만했던거같다.

트리가 존재한다. Misha는 $s$->$f$를 가며 정점에 낙서를 하고 Grisha는 $f$->$t$를 이동하며 
낙서된 정점의 수를 세는데 Grisha가 세는 정점의 수의 최댓값을 구하는 문제이다.

각 쿼리에는 $s$, $f$, $t$의 후보 $a$, $b$, $c$가 주어진다.

대회때는 2820 자동차 공장풀듯이 세그먼트트리와 lazy propogation 그리고 LCA를 이용해서 풀려고 했지만 끝나고서야 오류를 알았다.

사실은 LCA만으로 풀리는 문제이다.
1. LCA($s$, $f$) == LCA($f$, $t$) == $f$인 경우
2. LCA($s$, $f$) == LCA($f$, $t$) != $f$인 경우
3. LCA($s$, $f$) != LCA($f$, $t$)인 경우에 대해 따져주면 된다.

쿼리에서 입력으로 들어오는 후보가 3개밖에없으니 $3!$으로 모든 경우에 대해 돌려주어 최댓값을 구하면 된다.
int solve(int s, int f, int t) {
    int ret = 0;
    int sf_lca = getLCA(s, f), ft_lca = getLCA(f, t);
    if (sf_lca == ft_lca && sf_lca == f) ret = depth[getLCA(s, t)] - depth[f];
    else if (sf_lca == ft_lca) {
        int common_lca = sf_lca;
        ret = depth[getLCA(s, t)] - depth[common_lca] + depth[f] - depth[common_lca];
    }
    else{
        if (depth[sf_lca] > depth[ft_lca]) ret = depth[f] - depth[sf_lca];
        else ret = 0;
    }
    return ret + 1;
}
cs