1+ /*
2+ * Принцип работы алгоритма:
3+ * Нужно найти максимальное остовное дерево, поэтому используем алгоритм Краскала
4+ * в обратном порядке: сортируем все рёбра по убыванию веса и последовательно
5+ * добавляем ребро в ответ, если оно соединяет две разные компоненты связности.
6+ * Для проверки компонент используем DSU.
7+ *
8+ * Петли не влияют на ответ: ребро из вершины в саму себя не соединяет разные
9+ * компоненты, поэтому DSU его не добавит. Кратные рёбра обрабатываются естественно:
10+ * из них раньше будут рассмотрены более тяжёлые.
11+ *
12+ * Почему алгоритм корректен:
13+ * В алгоритме Краскала на каждом шаге выбирается самое тяжёлое ребро, которое
14+ * не создаёт цикл. По свойству разреза для максимального остовного дерева такое
15+ * ребро можно безопасно добавить в некоторый оптимальный остов. Повторяя этот
16+ * шаг, получаем максимальное остовное дерево. Если после обработки всех рёбер
17+ * выбрано меньше n - 1 ребра, значит граф несвязный и остовного дерева нет.
18+ *
19+ * Временная сложность: O(m log m) из-за сортировки рёбер.
20+ * Операции DSU работают почти за O(1), точнее O(alpha(n)).
21+ * Пространственная сложность: O(n + m), где n — число вершин, m — число рёбер.
22+ */
23+
24+ import java .io .EOFException ;
25+ import java .io .IOException ;
26+ import java .io .InputStream ;
27+ import java .io .OutputStream ;
28+ import java .util .Arrays ;
29+
30+ public class DorogayaSet {
31+
32+ static final String FAIL = "Oops! I did it again" ;
33+
34+ static long solve (int n , Edge [] edges ) {
35+ Arrays .sort (edges , (a , b ) -> Integer .compare (b .weight , a .weight ));
36+
37+ DSU dsu = new DSU (n );
38+ long totalWeight = 0 ;
39+ int usedEdges = 0 ;
40+
41+ for (Edge edge : edges ) {
42+ if (dsu .union (edge .from , edge .to )) {
43+ totalWeight += edge .weight ;
44+ usedEdges ++;
45+
46+ if (usedEdges == n - 1 ) {
47+ break ;
48+ }
49+ }
50+ }
51+
52+ if (usedEdges != n - 1 ) {
53+ return -1 ;
54+ }
55+
56+ return totalWeight ;
57+ }
58+
59+ static final class Edge {
60+ final int from ;
61+ final int to ;
62+ final int weight ;
63+
64+ Edge (int from , int to , int weight ) {
65+ this .from = from ;
66+ this .to = to ;
67+ this .weight = weight ;
68+ }
69+ }
70+
71+ static final class DSU {
72+ private final int [] parent ;
73+ private final int [] size ;
74+
75+ DSU (int n ) {
76+ parent = new int [n + 1 ];
77+ size = new int [n + 1 ];
78+
79+ for (int i = 1 ; i <= n ; i ++) {
80+ parent [i ] = i ;
81+ size [i ] = 1 ;
82+ }
83+ }
84+
85+ int find (int v ) {
86+ if (parent [v ] != v ) {
87+ parent [v ] = find (parent [v ]);
88+ }
89+ return parent [v ];
90+ }
91+
92+ boolean union (int a , int b ) {
93+ int rootA = find (a );
94+ int rootB = find (b );
95+
96+ if (rootA == rootB ) {
97+ return false ;
98+ }
99+
100+ if (size [rootA ] < size [rootB ]) {
101+ int tmp = rootA ;
102+ rootA = rootB ;
103+ rootB = tmp ;
104+ }
105+
106+ parent [rootB ] = rootA ;
107+ size [rootA ] += size [rootB ];
108+ return true ;
109+ }
110+ }
111+
112+ // -------------------- FAST INPUT --------------------
113+ static final class FastIn {
114+ private final InputStream in ;
115+ private final byte [] buf = new byte [1 << 16 ];
116+ private int ptr = 0 ;
117+ private int len = 0 ;
118+
119+ FastIn (InputStream in ) {
120+ this .in = in ;
121+ }
122+
123+ private int read () throws IOException {
124+ if (ptr >= len ) {
125+ len = in .read (buf );
126+ ptr = 0 ;
127+
128+ if (len <= 0 ) {
129+ return -1 ;
130+ }
131+ }
132+
133+ return buf [ptr ++];
134+ }
135+
136+ int nextInt () throws IOException {
137+ int c ;
138+
139+ do {
140+ c = read ();
141+
142+ if (c == -1 ) {
143+ throw new EOFException ("Unexpected EOF" );
144+ }
145+ } while (c <= ' ' );
146+
147+ int sign = 1 ;
148+
149+ if (c == '-' ) {
150+ sign = -1 ;
151+ c = read ();
152+ }
153+
154+ int val = 0 ;
155+
156+ while (c > ' ' ) {
157+ val = val * 10 + (c - '0' );
158+ c = read ();
159+ }
160+
161+ return val * sign ;
162+ }
163+ }
164+
165+ // -------------------- FAST OUTPUT --------------------
166+ static final class FastOut {
167+ private final OutputStream out ;
168+ private final byte [] buf = new byte [1 << 16 ];
169+ private int p = 0 ;
170+ private final byte [] tmp = new byte [20 ];
171+
172+ FastOut (OutputStream out ) {
173+ this .out = out ;
174+ }
175+
176+ void writeByte (int b ) throws IOException {
177+ if (p == buf .length ) {
178+ flush ();
179+ }
180+
181+ buf [p ++] = (byte ) b ;
182+ }
183+
184+ void writeString (String s ) throws IOException {
185+ for (int i = 0 ; i < s .length (); i ++) {
186+ writeByte (s .charAt (i ));
187+ }
188+ }
189+
190+ void writeLong (long x ) throws IOException {
191+ if (x == 0 ) {
192+ writeByte ('0' );
193+ return ;
194+ }
195+
196+ if (x < 0 ) {
197+ writeByte ('-' );
198+ x = -x ;
199+ }
200+
201+ int k = 0 ;
202+
203+ while (x > 0 ) {
204+ tmp [k ++] = (byte ) ('0' + (x % 10 ));
205+ x /= 10 ;
206+ }
207+
208+ for (int i = k - 1 ; i >= 0 ; i --) {
209+ writeByte (tmp [i ]);
210+ }
211+ }
212+
213+ void flush () throws IOException {
214+ out .write (buf , 0 , p );
215+ p = 0 ;
216+ }
217+ }
218+
219+ private static void run () throws Exception {
220+ FastIn in = new FastIn (System .in );
221+ FastOut out = new FastOut (System .out );
222+
223+ int n = in .nextInt ();
224+ int m = in .nextInt ();
225+
226+ Edge [] edges = new Edge [m ];
227+
228+ for (int i = 0 ; i < m ; i ++) {
229+ int from = in .nextInt ();
230+ int to = in .nextInt ();
231+ int weight = in .nextInt ();
232+
233+ edges [i ] = new Edge (from , to , weight );
234+ }
235+
236+ long answer = solve (n , edges );
237+
238+ if (answer == -1 ) {
239+ out .writeString (FAIL );
240+ } else {
241+ out .writeLong (answer );
242+ }
243+
244+ out .writeByte ('\n' );
245+ out .flush ();
246+ }
247+
248+ private static void test () {
249+ assertEq (19 , solve (4 , new Edge []{
250+ new Edge (1 , 2 , 5 ),
251+ new Edge (1 , 3 , 6 ),
252+ new Edge (2 , 4 , 8 ),
253+ new Edge (3 , 4 , 3 )
254+ }));
255+
256+ assertEq (3 , solve (3 , new Edge []{
257+ new Edge (1 , 2 , 1 ),
258+ new Edge (1 , 2 , 2 ),
259+ new Edge (2 , 3 , 1 )
260+ }));
261+
262+ assertEq (-1 , solve (2 , new Edge []{}));
263+
264+ assertEq (0 , solve (1 , new Edge []{}));
265+
266+ assertEq (10 , solve (2 , new Edge []{
267+ new Edge (1 , 1 , 100 ),
268+ new Edge (1 , 2 , 10 ),
269+ new Edge (2 , 2 , 100 )
270+ }));
271+
272+ assertEq (0 , solve (3 , new Edge []{
273+ new Edge (1 , 2 , 0 ),
274+ new Edge (2 , 3 , 0 )
275+ }));
276+
277+ System .out .println ("Test OK" );
278+ }
279+
280+ static void assertEq (long exp , long act ) {
281+ if (exp != act ) {
282+ throw new AssertionError ("Expected=" + exp + ", actual=" + act );
283+ }
284+ }
285+
286+ public static void main (String [] args ) throws Exception {
287+ if (System .getProperty ("os.name" ).startsWith ("Windows" )) {
288+ test ();
289+ } else {
290+ run ();
291+ }
292+ }
293+ }
0 commit comments