cubelover의 블로그

Tree Optimization

...2018. 8. 12. 18:38

트리에서 사용할 수 있는 특수한 몇몇 알고리즘들은 실제 시간복잡도가 생각한 것보다 작을 수 있다. 이 글에서는 그런 몇몇 케이스를 다룬다.


~


노드 개수가 \(A\)인 서브트리의 결과와 노드 개수가 \(B\)인 서브트리의 결과를 \(O(AB)\)의 시간에 합칠 수 있는 경우


최악의 경우 \(A\)와 \(B\)가 모두 \(O(N)\), 합치는 횟수가 \(O(N)\)이므로 \(O(N^3)\)인 것 같지만, 사실 \(O(N^2)\)이다.


~


증명


노드 개수가 \(N\)인 서브트리의 결과를 만드는 데 필요한 시간을 \(T(N)\)이라고 정의하자.


또한, 노드 개수가 \(A\)인 서브트리의 결과와 노드 개수가 \(B\)인 서브트리의 결과를 합치는 데 \(cAB\) (\(c\)는 상수) 이하의 시간이 필요하다고 하자.


\(N<A+B\)인 모든 \(N\)에 대해 \(T(N) \le cN^2\)이라고 가정하면,


\[T(A+B) \le T(A) + T(B) + cAB \le cA^2 + 2cAB + cB^2 = c(A+B)^2\]


따라서 수학적 귀납법에 의해 시간복잡도가 \(O(N^2)\)이 된다.


~


응용


이 아이디어로 ACM-ICPC Daejeon Regional 2012 J번(Slicing Tree), Coder's high 2016 Round 2: Nexon Arena C번(트리의 변화), shake! 2017 F번(단신쓴짠)을 풀 수 있다.


~


루트가 있는 트리에서, 각 노드마다 (자식들 중 높이가 두 번째로 높은 노드의 높이 + 높이가 세 번째로 높은 노드의 높이 + ... + 높이가 가장 낮은 노드의 높이)의 합으로 문제를 풀 수 있는 경우


이 경우 시간복잡도가 \(O(N)\)이다.


~


증명


노드 \(u\)의 높이를 \(h_u\)라고 하자. 높이의 정의에 따라, 노드 \(u\)의 자식들 \(v\)에 대해 \(h_u = \max_v \{h_v\} + 1\)이다.


또한, 루트를 제외하고 모든 노드의 부모는 단 한 개이므로, 트리의 루트를 \(r\)이라고 하면 모든 노드에 대해 모든 자식들의 높이 합은 다음과 같다.


\[\sum_{u \neq r} h_u = \sum_u h_u - h_r\]


모든 노드에 대해 두 번째, 세 번째, ...로 높이가 높은 노드의 높이 합은 전체 높이 합에서 첫 번째로 높이가 높은 노드의 높이 합을 뺀 것과 동일하다.


\[\sum_{u \neq r} h_u - \sum_u \max_v \{h_v\} = \sum_u h_u - \sum_u \max_v \{h_v\} - h_r = \sum_u ( h_u - \max_v \{h_v\} ) - h_r = N - h_r = O(N)\]


~


응용


이 아이디어로 가중치 없는 트리에서 세 정점의 순서쌍 (u, v, w)에 대하여, (u-v의 거리) = (v-w의 거리) = (w-u의 거리)를 만족하는 순서쌍이 몇 개나 있는지를 \(O(N)\)으로 구할 수 있다.


이 문제를 \(O(N^2)\)으로 풀면 만점이 나오는 버전이 POI에 출제되었다.


Archive: https://main.edu.pl/en/archive/oi/21/hot

Baekjoon Online Judge: https://www.acmicpc.net/problem/10122


Baekjoon Online Judge에서 \(O(N)\) 풀이로 0ms를 받았으니, 관심이 있는 분은 \(O(N^2)\) 풀이로 맞은 뒤 코드를 읽어보시기 바랍니다.

'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 13538  (3) 2016.11.03
Baekjoon Online Judge 1659  (0) 2016.02.02

ONTAK 2010 Day 7 Generator

...2018. 7. 17. 04:30

입력으로 정수 i(0 ≤ i ≤ 10)가 들어오면, genzaw.zip의 gen$i.out에 있는 내용을 출력하는 문제이다. 단, 소스코드는 10만자를 넘을 수 없다.


그래서 genzaw.zip을 까 보면, 가장 작은 gen0.out을 제외하고 200KB 정도 되는 파일부터 3MB 정도 되는 파일까지 다양하다. 물론 이걸 그대로 출력할 수는 없고, 파일 형태를 보고 생성한 알고리즘을 유추해서 문제를 풀어야 한다.


각 파일마다 푸는 방법이 많이 다르므로, 파일마다 힌트와 풀이를 정리해서 올린다.


~


gen0.out


예제 출력에 해당한다.




~


gen1.out


알파벳이 적힌 파일이다.




~


gen2.out


수열이 적힌 파일이다.




~


gen3.out


#과 .이 적힌 파일이다.




~


gen4.out


0과 1이 적힌 파일이다.




~


gen5.out


폴란드어가 적힌 파일이다.




~


gen6.out


T[$number]="$string";이 적힌 파일이다.




~


gen7.out


#과 .이 적힌 파일이다.




~


gen8.out


#과 .이 적힌 파일이다.




~


gen9.out


#과 .이 적힌 파일이다.




~


gen10.out


수열과 행렬이 적힌 파일이다.




'...' 카테고리의 다른 글

Tree Optimization  (2) 2018.08.12
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 13538  (3) 2016.11.03
Baekjoon Online Judge 1659  (0) 2016.02.02

BOJ Achievements

...2018. 5. 17. 23:18

NORMAL


100문제 이상 풀기

1000회 이상 제출하기

하루에 10문제 이상 풀기

7일 연속으로 매일 문제 풀기

북마크 기능 사용하기

한 게시글에 좋아요 1개 이상 받기

학교/회사 등록하기

슬랙에서 이야기하기

풀이 작성하기

그룹 만들기

문제 만들기

데이터 추가하기

대회 참가하기

나만 푼 문제 만들기

맞은 사람 목록 맨 위에 올라가기

숏코딩 목록 맨 위에 올라가기

Daily 로또(10948번)에서 맞았습니다 받기

Text로 10문제 이상 풀기


~


HARD


1000문제 이상 풀기

10000회 이상 제출하기

하루에 30문제 이상 풀기
30일 연속으로 매일 문제 풀기

한 게시글에 좋아요 10개 이상 받기

아이디 변경하기

블로그에 글 쓰기

대회 개최하기

스페셜 저지 추가하기

틀렸던 코드 재채점으로 맞았습니다 받기

데이터를 추가해서 맞은 사람 100명 이상 감소시키기

시간제한과 동일한 수행시간으로 맞았습니다 받기

수행시간, 메모리 사용량, 코드 길이 모두 동일한 결과 받기

모든 언어로 맞았습니다 받기

모든 문제에 제출하기

언어 랭킹 1위 달성하기

랜덤 게임~~(10944번)에서 맞았습니다 받기

Text로 50문제 이상 풀기


~


INSANE


10000문제 풀기

하루에 100문제 이상 풀기

365일 연속으로 매일 문제 풀기

한 게시글에 좋아요 100개 이상 받기

모든 언어로 모든 결과 받기

로또(10947번)에서 100점 받기

랜덤 게임~~~~(10946번)에서 100점 받기

Text로 100문제 이상 풀기

'...' 카테고리의 다른 글

Tree Optimization  (2) 2018.08.12
ONTAK 2010 Day 7 Generator  (0) 2018.07.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 13538  (3) 2016.11.03
Baekjoon Online Judge 1659  (0) 2016.02.02

Ubuntu PPTP VPN Server

...2017. 12. 17. 01:38

pptpd 설치


다음 명령어를 실행한다.


sudo apt-get install pptpd


~


ip 설정


/etc/pptpd.conf 파일의 맨 마지막에 다음을 추가한다.


localip 192.168.0.1
remoteip 192.168.0.100-200


~


DNS 설정


/etc/ppp/pptpd-options 파일의 ms-dns 항목을 주석해제하고 수정한다.


ms-dns 8.8.8.8
ms-dns 8.8.4.4


~


사용자 추가


/etc/ppp/chap-secrets 파일에 사용자를 추가한다. "(아이디) pptpd (비밀번호) (IP)" 형식으로 한 줄에 하나씩 적으면 된다.


cubelover pptpd p4ssw0rd 192.168.0.123
guest pptpd guest *

~


interface 이름 알아내기


다음 명령어를 실행하면 interface가 여러 개 나오는데, 이 중 vpn에 routing할 interface의 이름을 찾는다. 유선랜을 사용하는 경우 보통 eth0지만 아닌 경우도 있다.


ifconfig -a


~


iptables 설정


앞에서 알아낸 interface의 이름을 (interface)에 넣고 실행한다.


iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o (interface) -j MASQUERADE


~


ip forwarding 설정


/etc/sysctl.conf 파일에서 net.ipv4.ip_forward=1 항목을 주석해제한다. 그리고 다음 명령어를 실행한다.


sudo sysctl -p


~


pptpd 재시작


다음 명령어를 실행한다.


sudo pptpd restart


~


UFW 설정 (UFW를 사용하는 경우)


/etc/default/ufw 파일에서 DEFAULT_FORWARD_POLICY를 ACCEPT로 고쳐준다.


DEFAULT_FORWARD_POLICY="ACCEPT"


/etc/ufw/before.rules 파일에서 *filter 앞 부분에 다음을 추가한다.


# nat Table rules
*nat
:POSTROUTING ACCEPT [0:0]

# Allow traffic from clients to eth0
-A POSTROUTING -s 192.168.0.0/24 -o eth0 -j MASQUERADE

# commit to apply changes
COMMIT


그리고 # drop INVALID packets 바로 다음에 다음을 추가한다.


-A ufw-before-input -p 47 -i $iface -j ACCEPT


다음 명령어를 실행한다.


sudo ufw allow 1723
sudo ufw disable
sudo ufw enable


~


여기까지 잘 동작하는 지 확인한다. 잘 동작한다면 재부팅할 때 iptable이 날아가는 것을 방지하기 위해서 저장해뒀다가 부팅할 때 불러오도록 해야 한다.


다음 명령어를 실행한다.


sudo su
iptables-save > /etc/iptables.rules
exit


/etc/rc.local 파일의 exit 0 이전에 다음을 추가한다.


/sbin/iptables-restore < /etc/iptables.rules

'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Baekjoon Online Judge 13538  (3) 2016.11.03
Baekjoon Online Judge 1659  (0) 2016.02.02
1 이상 N 이하의 정수에서 i의 빈도수  (0) 2016.01.27

Baekjoon Online Judge 13538

...2016. 11. 3. 22:55

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


이 문제는 Persistent Segment Tree를 이용하면 효율적으로 해결할 수 있다.


수의 범위가 1~500000이므로 2^19(524288)개의 리프 노드를 만들고, 각 노드는 해당하는 구간에 포함되는 원소의 개수를 들고 있는다.


즉, k번째 Segment Tree의 [l, r) 노드는 1~k번째 원소 중 l 이상 r 미만인 수의 개수이다.


이렇게 트리를 만들면 각 쿼리는 다음과 같은 방법으로 처리할 수 있다.


0. 0번 트리를 만든다. 수열이 비어있으므로 모든 노드는 0을 값으로 갖는다.


1. 새로운 트리를 만든다. 이 트리는 현재까지의 수열에 x를 추가한 트리이고, 이를 N번 트리로 명명한다.


2. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 x와 xor한 값이 더 큰 방향에 원소가 있으면 해당 방향으로, 없다면 반대 방향으로 움직이는 것을 19번 반복하면 y를 구할 수 있다.


3. N -= k


4. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 x+1을 찾아가면서, 오른쪽 자식으로 갈 때 R번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 더해주고 L-1번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 빼 주는 것을 19번 반복하면 x보다 작거나 같은 원소의 개수를 구할 수 있다.


5. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 R번 트리에서 왼쪽 자식에 포함되는 원소의 개수에서 L-1번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 뺀 것이 k 이상이라면 왼쪽 자식으로 이동하고, 아니라면 그 값을 k에서 뺀 뒤 오른쪽 자식으로 이동한다. 이를 19번 반복해서 도착한 노드의 번호가 k번째로 작은 수이다.


전체 시간복잡도는 O(M log x)가 된다.

#include <cstdio>

struct node {
	node *l, *r;
	int x;
} T[11050000], *a[500005];
int Tn, n;

void Add(node *p, node *q, int x, int y) {
	p->x = q->x + 1;
	if (y < 0) return;
	if (x >> y & 1) {
		p->r = T + ++Tn;
		p->l = q->l;
		Add(p->r, q->r, x, y - 1);
	}
	else {
		p->l = T + ++Tn;
		p->r = q->r;
		Add(p->l, q->l, x, y - 1);
	}
}

int Xor(node *p, node *q, int x, int y) {
	if (y < 0) return 0;
	if (x >> y & 1 ? p->l->x == q->l->x : p->r->x != q->r->x) return 1 << y | Xor(p->r, q->r, x, y - 1);
	return Xor(p->l, q->l, x, y - 1);
}

int Sum(node *p, int x, int y) {
	if (y < 0) return 0;
	if (x >> y & 1) return p->l->x + Sum(p->r, x, y - 1);
	return Sum(p->l, x, y - 1);
}

int Kth(node *p, node *q, int x, int y) {
	if (y < 0) return 0;
	if (p->l->x - q->l->x < x) return 1 << y | Kth(p->r, q->r, x - p->l->x + q->l->x, y - 1);
	return Kth(p->l, q->l, x, y - 1);
}

int main() {
	int i, j, k, m;
	scanf("%d", &m);
	for (i = 1; i < 1 << 19; i++) {
		T[i].l = T + (i << 1);
		T[i].r = T + (i << 1 | 1);
	}
	a[n = 0] = T + 1;
	Tn = 1 << 20;
	while (m--) {
		scanf("%d", &i);
		switch (i) {
		case 1:
			scanf("%d", &i);
			a[++n] = T + ++Tn;
			Add(a[n], a[n - 1], i, 18);
			break;
		case 2:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Xor(a[j], a[i - 1], k, 18));
			break;
		case 3:
			scanf("%d", &i);
			n -= i;
			break;
		case 4:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Sum(a[j], k + 1, 18) - Sum(a[i - 1], k + 1, 18));
			break;
		case 5:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Kth(a[j], a[i - 1], k, 18));
			break;
		}
	}
}


'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 1659  (0) 2016.02.02
1 이상 N 이하의 정수에서 i의 빈도수  (0) 2016.01.27

Baekjoon Online Judge 1659

...2016. 2. 2. 04:47

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


정수로 이루어진 두 집합 S와 T가 있다. S와 T 간에 원소를 연결해서 S의 각 원소가 적어도 하나의 T의 원소와 연결되고, T의 각 원소가 적어도 하나의 S의 원소와 연결되어야 한다. a와 b를 연결할 때의 비용은 |a-b|이고, 전체 비용의 합을 최소화시키는 것이 목적이다.


앞으로 적어도 하나의 다른 원소와 연결되는 것을 '연결 조건을 만족한다'고 하자.


소정리 1. 만약 a와 b를 연결하고 c와 d를 연결하는 최적해가 존재하고 a<c이며 b>d라면, a와 d를 연결하고 b와 c를 연결하는 최적해가 존재한다.


이는 두 쌍의 연결 방법을 바꿈으로써 다른 원소가 영향받지 않고, 비용이 증가하지 않음에서 쉽게 알 수 있다.


이 정리 하나만으로 쉽게 O(nm) 풀이를 만들 수 있다. D[i][j] = (S에서 오름차순으로 i개, T에서 오름차순으로 j개가 연결 조건을 만족할 때 최소 비용)으로 정의하면, 다음과 같은 점화식을 통해 계산이 가능하다.


D[i][j] = min { D[i-1][j-1], D[i-1][j], D[i][j-1] } + |S[i]-T[j]|


그러나 이 방법으로는 제한이 500,000이기 때문에 쉽게 시간 초과가 발생한다.


최적해가 가지는 다른 특징을 찾아보기 위해 예제를 다음과 같이 그림으로 나타내 보았다.



여기서 우리는 다음과 같은 소정리를 하나 얻는다.


소정리 2. S의 원소 a<c와 T의 원소 b<d가 있을 때 a와 b를 연결하고 c와 d를 연결하는 최적해가 존재한다면, a와 d를 연결하거나 b와 c를 연결하지 않는 최적해가 존재한다.


이게 무슨 소리인지를 그림으로 나타내면 아래와 같다.



즉, 그림처럼 a와 b, c와 d가 연결되어 있다면 여기서 a와 d를 연결하거나 b와 c를 연결하는 것은 이득이 되지 않는다는 것이다. 당연한 소정리지만 덕분에 우리는 모든 연결을 두 종류로 나눌 수 있다.



빨간색 실선을 강한 연결, 회색 화살표를 약한 연결이라고 하자. 강한 연결은 S와 T에서 하나씩 선택해 연결한 것이며, 약한 연결은 S 또는 T의 원소를 가장 가까운 다른 원소에 연결한 것이다. 이로부터 O(n+m) 풀이를 이끌어낸다.


S와 T의 원소들을 통째로 오름차순으로 정렬하고, D[i] = (i번째 원소까지 연결 조건을 만족했을 때 최소 비용)이라고 정의하자. 그러면 두 가지 경우가 생김을 알 수 있다.


첫 번째 경우는 i번째 원소가 약한 연결로 연결될 때이다. 이 경우는 i번째 원소에서 가장 가까운 다른 원소를 연결한다.


두 번째 경우는 어떤 j에 대하여 j+1~i번째 원소가 모두 강한 연결로 일대일 대응될 때이다. 이 경우 j+1~i번째 원소 중 S에 속하는 원소의 개수와 T에 속하는 원소의 개수가 같아야 한다. 그런 j가 여러 개일 수 있는데, 이 때 가장 큰 j만 고려해도 충분함을 알 수 있다. 만약 더 작은 j'에 대해서 j'+1~i까지 모두 일대일 대응시키는 것이 최적해라면, 이는 D[j]를 계산할 때 j'+1~j까지 일대일 대응시키는 경우가 포함되어 있고, D[i]를 계산할 때 j+1~i까지 일대일 대응시키는 경우가 포함되어 있으므로 가장 큰 j만 고려해도 충분하다.


첫 번째 경우는 O(1)에 처리할 수 있지만, 두 번째 경우는 다소 어려워 보인다. 우리가 고려하는 j가 j+1~i번째 원소 중 S에 속하는 원소의 개수와 T에 속하는 원소의 개수가 같은 최대의 j이므로, j+1~i-1에서는 항상 S에 속하는 원소의 개수가 T에 속하는 원소의 개수보다 많거나, 아니면 그 반대이다. 따라서 j+1~i번째 원소를 일대일 대응하는 최소 비용은 |(S에 속하는 원소들의 합)-(T에 속하는 원소들의 합)|이 되므로, 이 또한 O(1)에 처리할 수 있다.


입력으로 S와 T의 원소가 오름차순으로 주어지므로 이를 병합하면서 위 과정을 수행하면 O(n+m)에 해결이 가능하다.

'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 13538  (3) 2016.11.03
1 이상 N 이하의 정수에서 i의 빈도수  (0) 2016.01.27

int frq(int n, int i) {
	int j, r = 0;
	for (j = 1; j <= n; j *= 10) if (n / j / 10 >= !i) r += (n / 10 / j - !i) * j + (n / j % 10 > i ? j : n / j % 10 == i ? n % j + 1 : 0);
	return r;
}

'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 13538  (3) 2016.11.03
Baekjoon Online Judge 1659  (0) 2016.02.02