codeforces:219D. Choosing Capital for Treeland
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了codeforces:219D. Choosing Capital for Treeland相关的知识,希望对你有一定的参考价值。
题目大意:国家由n个城市以及n-1条连接不同城市的道路组成(每条道路都有正向和逆向之分),并且每个城市到另外一个城市都至少存在一条路径。现在议会要决定选一个城市作为首都。当一个城市选为首都时,需要将所有从首都到其它城市的路径上的所有边都是正向的(如果不是正向的则需要颠转道路)。求这样的首都,使得需要颠转的道路数目最小。
其中2<=n<=2e5。
首先这显然是一副无向无环连通图(参考我的博客连通图的一些性质)。因此从任意一个城市出发到另外一个城市都有唯一一条路径。
为了后面分析的简便,这里记选取城市x为首都需要颠转的道路数目为x的费用,记作x.c。需要考虑这样一个思路,假设A和B是两个之间存在道路的城市,且道路由A指向B(即从A到B是正向的),那么城市A和B必定有这样的关系:A.c + 1= B.c。说明一下为什么,当选取A作为首都时,尝试将所有需要颠转的边进行颠转,共颠转了A.c条边,此时可以认为所有从A出发到其它城市的路径中只包含正向边。而当选取B作为首都时,那么我们只需要在上面的基础上再颠转A与B之间的边。对于原本所有从A出发到达的城市的路径中包含B的路径终点C,由于路径上只有正向边,因此从B到C也可以只沿着正向边到达;而对于原本所有从A出发到达的城市的路径中不包含B的路径终点D,由于此时B,A,...,D这些城市之间均由正向边相连,故B到D的路径只有正向边;对于城市A,由于B,A是正向(颠转的结果),故B到A的路径只有正向边。故我们得出最多颠转A.c+1条边就可以保证B作为首都,即B.c <= A.c + 1。而对于B作为首都,颠转了共B.c条边,那么当选取A作为首都时,只需要恢复A与B之间的边即可使得A到达其它城市的路径只有正向边,而这里的证明与前面基本一致,得出的结论是A.c <= B.c - 1,而合并的结论是A.c + 1= B.c。
利用前面的分析的结果,利用n-1条边的方向,我们可以得出n个城市的关系。选取任意一个城市X做为参照,利用广度优先搜索算法在O(n)的时间复杂度内可以计算剩余n-1个城市相对于城市X的费用,而同样利用广度优先搜索算法可以在O(n)的时间复杂度内计算出城市X的费用。最后的输出只需要组装相对费用和X的费用即可得出。总的时间复杂度为O(n),而空间复杂度由于使用了广度优先搜索和保存边的信息,因此为O(n)。
下面给出代码:
1 import java.io.BufferedInputStream; 2 import java.io.IOException; 3 import java.io.InputStream; 4 import java.io.PushbackInputStream; 5 import java.math.BigDecimal; 6 import java.util.LinkedList; 7 import java.util.List; 8 import java.util.Queue; 9 10 /** 11 * Created by Administrator on 2017/8/23. 12 */ 13 public class ChoosingCapitalForTreeland { 14 int n; 15 Node[] nodes; 16 17 public static void main(String[] args) throws Exception { 18 new ChoosingCapitalForTreeland().solve(); 19 } 20 21 public void solve() throws Exception { 22 init(); 23 24 Pair pair = bfs(0); 25 26 int min = Integer.MAX_VALUE; 27 for (int i = 0; i < n; i++) { 28 min = Math.min(min, pair.needInverse[i]); 29 } 30 StringBuilder possibleNode = new StringBuilder(); 31 for (int i = 0; i < n; i++) { 32 if (pair.needInverse[i] != min) { 33 continue; 34 } 35 possibleNode.append(i + 1); 36 possibleNode.append(‘ ‘); 37 } 38 possibleNode.setLength(possibleNode.length() - 1); 39 min += pair.offset; 40 System.out.println(min); 41 System.out.println(possibleNode.toString()); 42 } 43 44 public Pair bfs(int src) { 45 int offset = 0; 46 int[] needInverse = new int[n]; 47 48 final int NONE = Integer.MIN_VALUE; 49 for (int i = 0; i < n; i++) { 50 needInverse[i] = NONE; 51 } 52 53 Queue<Node> queue = new LinkedList<>(); 54 needInverse[src] = 0; 55 queue.add(nodes[src]); 56 while (!queue.isEmpty()) { 57 Node cur = queue.poll(); 58 int curNeedInverse = needInverse[cur.id]; 59 for (Node direct : cur.direct) { 60 if (needInverse[direct.id] != NONE) { 61 continue; 62 } 63 needInverse[direct.id] = curNeedInverse + 1; 64 queue.add(direct); 65 } 66 for (Node inverse : cur.inverse) { 67 if (needInverse[inverse.id] != NONE) { 68 continue; 69 } 70 needInverse[inverse.id] = curNeedInverse - 1; 71 offset++; 72 queue.add(inverse); 73 } 74 } 75 76 Pair result = new Pair(); 77 result.needInverse = needInverse; 78 result.offset = offset; 79 return result; 80 } 81 82 public void init() throws Exception { 83 AcmInputReader input = new AcmInputReader(System.in); 84 n = input.nextInteger(); 85 nodes = new Node[n]; 86 for (int i = 0; i < n; i++) { 87 nodes[i] = new Node(); 88 nodes[i].id = i; 89 } 90 for (int i = 1; i < n; i++) { 91 int from = input.nextInteger() - 1; 92 int to = input.nextInteger() - 1; 93 nodes[from].direct.add(nodes[to]); 94 nodes[to].inverse.add(nodes[from]); 95 } 96 } 97 98 static class Node { 99 int id; 100 List<Node> direct = new LinkedList<>(); 101 List<Node> inverse = new LinkedList<>(); 102 103 @Override 104 public String toString() { 105 return "" + id; 106 } 107 } 108 109 static class Pair { 110 int offset; 111 int[] needInverse; 112 } 113 114 /** 115 * @author dalt 116 * @see java.lang.AutoCloseable 117 * @since java1.7 118 */ 119 static class AcmInputReader implements AutoCloseable { 120 private PushbackInputStream in; 121 122 /** 123 * 创建读取器 124 * 125 * @param input 输入流 126 */ 127 public AcmInputReader(InputStream input) { 128 in = new PushbackInputStream(new BufferedInputStream(input)); 129 } 130 131 @Override 132 public void close() throws IOException { 133 in.close(); 134 } 135 136 private int nextByte() throws IOException { 137 return in.read() & 0xff; 138 } 139 140 /** 141 * 如果下一个字节为b,则跳过该字节 142 * 143 * @param b 被跳过的字节值 144 * @throws IOException if 输入流读取错误 145 */ 146 public void skipByte(int b) throws IOException { 147 int c; 148 if ((c = nextByte()) != b) { 149 in.unread(c); 150 } 151 } 152 153 /** 154 * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times} 155 * 156 * @param b 被跳过的字节值 157 * @param times 跳过次数,-1表示无穷 158 * @throws IOException if 输入流读取错误 159 */ 160 public void skipByte(int b, int times) throws IOException { 161 int c; 162 while ((c = nextByte()) == b && times > 0) { 163 times--; 164 } 165 if (c != b) { 166 in.unread(c); 167 } 168 } 169 170 /** 171 * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。 172 * 173 * @param b 被跳过的字节值 174 * @param times 跳过次数,-1表示无穷 175 * @throws IOException if 输入流读取错误 176 */ 177 public void skipBlankAndByte(int b, int times) throws IOException { 178 int c; 179 skipBlank(); 180 while ((c = nextByte()) == b && times > 0) { 181 times--; 182 skipBlank(); 183 } 184 if (c != b) { 185 in.unread(c); 186 } 187 } 188 189 /** 190 * 读取下一块不含空白字符的字符块 191 * 192 * @return 下一块不含空白字符的字符块 193 * @throws IOException if 输入流读取错误 194 */ 195 public String nextBlock() throws IOException { 196 skipBlank(); 197 StringBuilder sb = new StringBuilder(); 198 int c = nextByte(); 199 while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) { 200 sb.append((char) c); 201 } 202 in.unread(c); 203 return sb.toString(); 204 } 205 206 /** 207 * 跳过输入流中后续空白字符 208 * 209 * @throws IOException if 输入流读取错误 210 */ 211 private void skipBlank() throws IOException { 212 int c; 213 while ((c = nextByte()) <= 32) ; 214 in.unread(c); 215 } 216 217 /** 218 * 读取下一个整数(可正可负),这里没有对溢出做判断 219 * 220 * @return 下一个整数值 221 * @throws IOException if 输入流读取错误 222 */ 223 public int nextInteger() throws IOException { 224 skipBlank(); 225 int value = 0; 226 boolean positive = true; 227 int c = nextByte(); 228 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 229 positive = c == ‘+‘; 230 } else { 231 value = ‘0‘ - c; 232 } 233 c = nextByte(); 234 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 235 value = (value << 3) + (value << 1) + ‘0‘ - c; 236 c = nextByte(); 237 } 238 239 in.unread(c); 240 return positive ? -value : value; 241 } 242 243 /** 244 * 判断是否到了文件结尾 245 * 246 * @return true如果到了文件结尾,否则false 247 * @throws IOException if 输入流读取错误 248 */ 249 public boolean isMeetEOF() throws IOException { 250 int c = nextByte(); 251 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 252 return true; 253 } 254 in.unread(c); 255 return false; 256 } 257 258 /** 259 * 判断是否在跳过空白字符后抵达文件结尾 260 * 261 * @return true如果到了文件结尾,否则false 262 * @throws IOException if 输入流读取错误 263 */ 264 public boolean isMeetBlankAndEOF() throws IOException { 265 skipBlank(); 266 int c = nextByte(); 267 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 268 return true; 269 } 270 in.unread(c); 271 return false; 272 } 273 274 /** 275 * 获取下一个用英文字母组成的单词 276 * 277 * @return 下一个用英文字母组成的单词 278 */ 279 public String nextWord() throws IOException { 280 StringBuilder sb = new StringBuilder(16); 281 skipBlank(); 282 int c; 283 while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) { 284 sb.append((char) c); 285 } 286 in.unread(c); 287 return sb.toString(); 288 } 289 290 /** 291 * 读取下一个长整数(可正可负),这里没有对溢出做判断 292 * 293 * @return 下一个长整数值 294 * @throws IOException if 输入流读取错误 295 */ 296 public long nextLong() throws IOException { 297 skipBlank(); 298 long value = 0; 299 boolean positive = true; 300 int c = nextByte(); 301 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 302 positive = c == ‘+‘; 303 } else { 304 value = ‘0‘ - c; 305 } 306 c = nextByte(); 307 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 308 value = (value << 3) + (value << 1) + ‘0‘ - c; 309 c = nextByte(); 310 } 311 in.unread(c); 312 return positive ? -value : value; 313 } 314 315 /** 316 * 读取下一个浮点数(可正可负),浮点数是近似值 317 * 318 * @return 下一个浮点数值 319 * @throws IOException if 输入流读取错误 320 */ 321 public float nextFloat() throws IOException { 322 return (float) nextDouble(); 323 } 324 325 /** 326 * 读取下一个浮点数(可正可负),浮点数是近似值 327 * 328 * @return 下一个浮点数值 329 * @throws IOException if 输入流读取错误 330 */ 331 public double nextDouble() throws IOException { 332 skipBlank(); 333 double value = 0; 334 boolean positive = true; 335 int c = nextByte(); 336 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 337 positive = c == ‘+‘; 338 } else { 339 value = c - ‘0‘; 340 } 341 c = nextByte(); 342 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 343 value = value * 10.0 + c - ‘0‘; 344 c = nextByte(); 345 } 346 347 if (c == ‘.‘) { 348 double littlePart = 0; 349 double base = 1; 350 c = nextByte(); 351 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 352 littlePart = littlePart * 10.0 + c - ‘0‘; 353 base *= 10.0; 354 c = nextByte(); 355 } 356 value += littlePart / base; 357 } 358 in.unread(c); 359 return positive ? value : -value; 360 } 361 362 /** 363 * 读取下一个高精度数值 364 * 365 * @return 下一个高精度数值 366 * @throws IOException if 输入流读取错误 367 */ 368 public BigDecimal nextDecimal() throws IOException { 369 skipBlank(); 370 StringBuilder sb = new StringBuilder(); 371 sb.append((char) nextByte()); 372 int c = nextByte(); 373 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 374 sb.append((char) c); 375 c = nextByte(); 376 } 377 if (c == ‘.‘) { 378 sb.append(‘.‘); 379 c = nextByte(); 380 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 381 sb.append((char) c); 382 c = nextByte(); 383 } 384 } 385 in.unread(c); 386 return new BigDecimal(sb.toString()); 387 } 388 389 private static class AsciiMarksLazyHolder { 390 public static final byte BLANK_MARK = 1; 391 public static final byte SIGN_MARK = 1 << 1; 392 public static final byte NUMERAL_MARK = 1 << 2; 393 public static final byte UPPERCASE_LETTER_MARK = 1 << 3; 394 public static final byte LOWERCASE_LETTER_MARK = 1 << 4; 395 public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK; 396 public static final byte EOF = 1 << 5; 397 public static byte[] asciiMarks = new byte[256]; 398 399 static { 400 for (int i = 0; i <= 32; i++) { 401 asciiMarks[i] = BLANK_MARK; 402 } 403 asciiMarks[‘+‘] = SIGN_MARK; 404 asciiMarks[‘-‘] = SIGN_MARK; 405 for (int i = ‘0‘; i <= ‘9‘; i++) { 406 asciiMarks[i] = NUMERAL_MARK; 407 } 408 for (int i = ‘a‘; i <= ‘z‘; i++) { 409 asciiMarks[i] = LOWERCASE_LETTER_MARK; 410 } 411 for (int i = ‘A‘; i <= ‘Z‘; i++) { 412 asciiMarks[i] = UPPERCASE_LETTER_MARK; 413 } 414 asciiMarks[0xff] = EOF; 415 } 416 } 417 } 418 }
以上是关于codeforces:219D. Choosing Capital for Treeland的主要内容,如果未能解决你的问题,请参考以下文章
codeforces:219D. Choosing Capital for Treeland
Codeforces 219D Choosing Capital for Treeland:Tree dp
Codeforces 219D Choosing Capital for Treeland?????????DP???
Codeforces 219D Choosing Capital for Treeland 2次DP