Loading [MathJax]/jax/output/CommonHTML/jax.js

Minggu, 10 Februari 2013

UVa 12440 - Save the Trees

Soal menarik yang akan dibahas kali ini adalah UVa 12440 - Save the Trees. Sedikit sekali diskusi yang ada di internet tentang soal ini, sehingga saya memutuskan untuk menulis pembahasannya.

Persoalannya adalah, diberikan sebuah barisan pohon (bisa sampai 100000) dengan ketinggian dan jenis tertentu. Tugas kita adalah mengelompokkan pohon-pohon konsekutif ke dalam beberapa grup, dimana banyakya grup tidak dibatasi dan di dalam grup tidak boleh ada pohon berjenis sama. Biaya untuk mengelompokkan sebuah grup sama dengan tinggi maksimum dari pohon di grup tersebut. Berapa biaya total minimal yang mungkin untuk mengelompokkan semua pohon tersebut?

Misalnya tinggi pohon ke-i dinyatakan dengan h[i], dan jenis pohon ke-i adalah c[i].
Observasi 1:
Untuk setiap pohon ke-i, ada pohon x dimana x adalah minimal dan x ≤ i, sedemikian sehingga c[j] untuk x ≤ j ≤ i unik. Kita sebut x ini sebagai pre[i].

Observasi 2:
Biaya mengelompokkan pohon dari 1 sampai i sama dengan biaya mengelompokkan pohon dari 1 sampai x-1 ditambah max(h[i],h[i-1],...,h[x]) dengan pre[i] ≤ x ≤ i. Di antara semua kemungkinan x, pilih yang biayanya minimal. Karena muncul sub-masalah, kita dapat menggunakan dynamic programming (DP). Formulasi DP-nya:
f(i)={0,i<1minpre[i]xi((maxxjih[j])+f(x1)),i1


Jawabannya ada di f(N).

Dengan formulasi DP tersebut, untuk kasus terburuk kompleksitasnya adalah O(N2) (ketika semua pohon berbeda jenis). Karena pasti TLE, kita butuh observasi yang lebih lanjut. Misalkan kita sudah mengetahui nilai-nilai f(0), f(1), f(2), ..., f(i-1) dan hendak mencari nilai f(i). Selain itu, kita sudah mengetahui nilai g(1,i-1), g(2,i-1), ..., g(i-1,i-1) dimana g(x,i) menyatakan ketinggian maksimum dari pohon-pohon dari x sampai i.

Observasi 3:
Misalkan cost[x] menyatakan nilai f(i) jika diputuskan untuk mengelompokkan pohon-pohon dari x hingga i, maka untuk setiap x, cost[x] = g(x,i) + f(x-1). Selanjutnya kita tinggal mencari nilai minimum dari cost[x] dengan pre[i] ≤ x ≤ i sebagai nilai dari f(i).

Observasi 4:
Jika kita sudah mengetahui nilai g(1,i-1), g(2,i-1), ..., g(i-1,i-1), nilai g(1,i), g(2, i), ..., g(i,i) dapat diketahui dengan bantuan struktur data stack. Teknik ini mirip dengan teknik yang digunakan di pembahasan soal Span. Kita akan membutuhkan update untuk suatu interval cost[x] karena adanya perubahan nilai fungsi g, yaitu saat i bertambah 1. Observasi ini merupakan bagian paling penting untuk mengoptimisasi algoritma kita.

Observasi 5:
Update untuk suatu interval dan melakukan ekstraksi nilai minimum dari suatu interval dapat dilakukan dengan bantuan struktur data segment tree, untuk mencapai kompleksitas O(logN) per operasi. Lebih jelasnya, kita butuh segment tree dengan kemampuan:
  • Tambah nilai dari indeks ke-a hingga indeks ke-b dengan c (bisa saja c negatif)
  • Cari nilai terkecil dari indeks ke-a hingga indeks ke-b

Dengan DP bottom-up dan bantuan segment tree itu, kita bisa capai kompleksitas O(NlogN). Karena setiap elemen hanya masuk ke dalam stack maksimal sekali, lalu keluar maksimal sekali. Setiap masuk atau keluar, dilakukan update terhadap array cost.
Untuk lebih jelasnya, perhatikan contoh kasus berikut:

Untuk mencari nilai f(9), terlebih dahulu kita mencari nilai-nilai g(i,9) dari g(i,8). Kita lakukan operasi:

  • Tambah nilai cost dari indeks ke-7 hingga indeks ke-8 dengan -3
  • Tambah nilai cost dari indeks ke-4 hingga indeks ke-6 dengan -4
  • Tambah nilai cost dari indeks ke-4 hingga indeks ke-9 dengan 5
  • Nilai f(9) adalah nilai terkecil dari cost indeks ke-6 sampai ke-9 (perhatikan bahwa pre[9] = 6), yaitu 14
  • Tambah nilai cost dari indeks ke-9 hingga indeks ke-9 dengan 14, sebagai bekal untuk mencari nilai f(10) (jika ada)

Berikut kode saya:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <utility>
#include <vector>
#include <set>
 
#define REP(a,b) for (int a = 0; a < b; a++)
#define FOR(a,b,c) for (int a = b; a <= c; a++)
#define RESET(a,b) memset(a,b,sizeof a)
 
#define MP make_pair
#define F first
#define S second
#define PII pair<int,int>
 
#define MAXN 131072
using namespace std;
 
int INF = 2123123123;
 
struct node{
   int val,prop;
};
 
int nkasus;
int n,h[MAXN],col[MAXN];
int dp[MAXN];
int pre[MAXN];
int stek[MAXN],p;
node stri[2*MAXN];
set<int> him;
 
void isiPre(){
   him.clear();
    
   int di = 1;
   FOR(i,1,n){
      while (him.count(col[i])){
         him.erase(col[di++]);
      }
      him.insert(col[i]);
       
      pre[i] = di;
   }
}
 
void build(int nod, int ki, int ka){
   stri[nod].val = 0;
   stri[nod].prop = 0;
    
   if (ki < ka){
      int tgh = (ki + ka) >> 1;
       
      build(2*nod+1, ki, tgh);
      build(2*nod+2, tgh+1, ka);
   }
}
 
void push(int x){
   stri[2*x+1].prop += stri[x].prop;
   stri[2*x+2].prop += stri[x].prop;
   stri[x].prop = 0;
}
 
int GET(int x){
   return stri[x].val + stri[x].prop;
}  
 
void update(int nod, int ki, int ka, int a, int b, int val){
   if ((a <= ki) && (ka <= b)){
      stri[nod].prop += val;
   }else{
      int tgh = (ki + ka) >> 1;
       
      push(nod);
       
      if (a <= tgh) update(2*nod+1, ki, tgh, a, b, val);
      if (b >  tgh) update(2*nod+2, tgh+1, ka, a, b, val);
       
      stri[nod].val = min(GET(2*nod+1), GET(2*nod+2));
   }
}
 
int query(int nod, int ki, int ka, int a, int b){
   if ((a <= ki) && (ka <= b)){
      return GET(nod);
   }else{
      int tgh = (ki + ka) >> 1;
      int ret = INF;
       
      push(nod);
       
      if (a <= tgh) ret = min(ret, query(2*nod+1, ki, tgh, a, b));
      if (b >  tgh) ret = min(ret, query(2*nod+2, tgh+1, ka, a, b));
       
      stri[nod].val = min(GET(2*nod+1), GET(2*nod+2));
       
      return ret;
   }
}
 
int main(){
   scanf("%d", &nkasus);
   REP(jt,nkasus){
      scanf("%d", &n);
      FOR(i,1,n){
         scanf("%d %d", &col[i], &h[i]);
      }
      h[0] = MAXN;
       
      isiPre();
       
      stek[0] = 0;
      p = 1;
       
      build(0,1,n);
       
      FOR(i,1,n){
         while (h[stek[p-1]] <= h[i]){
            update(0, 1, n, stek[p-2]+1, stek[p-1], -h[stek[p-1]]);
            p--;
         }
         stek[p++] = i;
         update(0, 1, n, stek[p-2]+1, stek[p-1], h[i]);
    
         dp[i] = query(0, 1, n, pre[i], i);
         update(0, 1, n, i+1, i+1, dp[i]);
      }
       
      printf("Case %d: %d\n", jt+1, dp[n]);
   }
    
   return 0;
}

Tidak ada komentar :

Posting Komentar