题目大意:
给出一个函数P,P接受一个数组A作为参数,并返回一个新的数组B,且B.length = A.length + 1,B[i] = SUM(A[0], ..., A[i])。有一个无穷数组序列A[0], A[1], ... 满足A[i]=P(A[i-1]),其中i为任意自然数。对于输入k和A[0],求一个最小的下标t,使得A[t]中包含不小于k的数值。
其中A[0].length <= 2e5, k <= 1e18,且A[0]中至少有两个正整数。
数学向的题目。本来以为是个找规律的题目,但是最后并不能找到,只好参考了官方题解的。总的来说很有趣的题目。
对于输入的A[0],首先需要把前面的0去除,因为这些0没有任何意义(若A[0][0], ..., A[0][t] = 0,则A[i][0], ... ,A[i][t] = 0),但是会拖慢程序。
接下来,可以依据A[0]的长度做判断。如果长度超过某个阈值U,则说明这个序列中最大值将会再后续的迭代中快速增长,增长速度与序列的长度L有关,我个人的估计是O(t^L)级别的,其中t为迭代次数,这里不给证明。因此可以暴力循环直到出现不小于k的元素即可。官方推荐的阈值为10。
而对于A[0]的长度不超过10的情况,需要借助矩阵来求解。由于P(x1,x2, ... , xn) = (x1, x1 + x2, ... , x1 + x2 + ... + xn),显然P是一个线性变换,线性代数教过每个线性变换都唯一对应一个矩阵。下面给出对应的线性变换的公式:$$ \left(\begin{matrix} 1 & 0 &\cdots & 0 & 0\\ 1 & 1 &\cdots & 0 & 0\\ \cdots &\cdots &\cdots &\cdots &\cdots\\ 1 & 1 &\cdots & 1 & 0\\ 1 & 1 &\cdots & 1 & 1 \end{matrix}\right)\cdot A\left[i\right]=A\left[i+1\right] $$
之后记变换矩阵为T。P^n(A[0])=T^n*A[0],其中T^n可以利用快速幂乘法计算得到(理由是矩阵的乘法运算是结合的)。之后利用二分查找法寻找最小的x,使得T^x*A[0]中存在不小于k的元素。在这个过程中为了提高效率,可以使用红黑树缓存中间计算过的矩阵。
最后说明一下时间复杂度,暴力破解部分不进行说明,只说明利用矩阵计算的部分。首先说明二分查找法迭代的次数,由于MAX(P(A[i]))>MAX(A[i]),因此最终解必然不可能超过k,而二分查找法的迭代次数则为O(log2(k))。而由于二分查找法每次迭代都需要计算中间值,中间值的矩阵M必须得到计算,借助缓存,可以认为形如T^(2^i)的矩阵均已经被缓存,故计算中间值矩阵的时间复杂度为O(log2(k))*O(n^3)=O(n^3log2(k)),而判断M*A[0]中是否有达到k的元素这一过程的时间复杂度可以不考虑(因为是小头)。故整个二分查找法的时间复杂度为O(log2(k))*O(n^3log2(k))=O(n^3(log2(k))^2),这在已知n<=10的前提下是可以接受的。
最后给出JAVA代码:
1 package cn.dalt.codeforces; 2 3 import java.io.BufferedInputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.PushbackInputStream; 7 import java.math.BigDecimal; 8 import java.util.Map; 9 import java.util.TreeMap; 10 11 /** 12 * Created by dalt on 2017/9/10. 13 */ 14 public class BruteForcePrefixSums { 15 int n; 16 long threshold; 17 long[] basic; 18 19 public static void main(String[] args) throws Exception { 20 BruteForcePrefixSums solution = new BruteForcePrefixSums(); 21 solution.init(); 22 long result = solution.solve(); 23 System.out.println(result); 24 } 25 26 public void init() throws Exception { 27 AcmInputReader input = new AcmInputReader(System.in); 28 n = input.nextInteger(); 29 threshold = input.nextLong(); 30 basic = new long[n]; 31 32 for (int i = 0; i < n; i++) { 33 basic[i] = input.nextLong(); 34 } 35 } 36 37 public long solve() { 38 //Remove prefix blank 39 { 40 int firstNotZero = 0; 41 while (basic[firstNotZero] == 0) { 42 firstNotZero++; 43 } 44 long[] tmp = new long[n - firstNotZero]; 45 System.arraycopy(basic, firstNotZero, tmp, 0, tmp.length); 46 n = tmp.length; 47 basic = tmp; 48 } 49 50 //Test whether A0 satisfy the threshold 51 for (long value : basic) { 52 if (value >= threshold) { 53 return 0; 54 } 55 } 56 57 //n is more than 18, so brute force 58 if (n >= 10) { 59 return bruteForceProcess(); 60 } 61 //Try fast matrix 62 else { 63 return binarySearch(basic); 64 } 65 } 66 67 public long binarySearch(long[] vector) { 68 long[][] a0 = new long[vector.length][vector.length]; 69 for (int i = 0, bound = vector.length; i < bound; i++) { 70 for (int j = 0; j <= i; j++) { 71 a0[i][j] = 1; 72 } 73 } 74 75 TreeMap<Long, long[][]> cache = new TreeMap<>(); 76 cache.put(1L, a0); 77 78 //Find lower bound and upper bound 79 long lowerAge = 0; 80 long[][] lowerMat = null; 81 long upperAge = 1; 82 long[][] upperMat = a0; 83 while (!contain(upperMat, vector)) { 84 lowerMat = upperMat; 85 lowerAge = upperAge; 86 cache.put(lowerAge, lowerMat); 87 upperMat = multiply(upperMat, upperMat); 88 upperAge *= 2; 89 } 90 91 //Binary search part 92 while (lowerAge < upperAge) { 93 long halfAge = (lowerAge + upperAge + 1) / 2; 94 95 //Calculate a0^half in fast way 96 Map.Entry<Long, long[][]> entry = cache.floorEntry(halfAge); 97 long remain = halfAge - entry.getKey(); 98 long[][] halfMat = entry.getValue(); 99 while (remain > 0) { 100 entry = cache.floorEntry(remain); 101 remain = remain - entry.getKey(); 102 halfMat = multiply(halfMat, entry.getValue()); 103 } 104 105 if (contain(halfMat, vector)) { 106 upperAge = halfAge - 1; 107 upperMat = halfMat; 108 } else { 109 lowerAge = halfAge; 110 lowerMat = halfMat; 111 } 112 } 113 return lowerAge + 1; 114 } 115 116 public boolean contain(long[][] mat, long[] vector) { 117 long[] result = new long[vector.length]; 118 int row = mat.length; 119 long max = 0; 120 int col = mat[0].length; 121 for (int i = 0; i < row; i++) { 122 long aggregation = 0; 123 for (int j = 0; j < col; j++) { 124 long value = mat[i][j] * vector[j]; 125 if (value >= threshold) { 126 return true; 127 } 128 aggregation += mat[i][j] * vector[j]; 129 } 130 if (aggregation < 0 || aggregation >= threshold) { 131 return true; 132 } 133 } 134 return false; 135 } 136 137 public long[][] multiply(long[][] a, long[][] b) { 138 long[][] result = new long[a.length][b[0].length]; 139 int row = a.length; 140 int col = b[0].length; 141 int mid = b.length; 142 for (int i = 0; i < row; i++) { 143 for (int j = 0; j < col; j++) { 144 long aggregation = 0; 145 for (int k = 0; k < mid; k++) { 146 aggregation += a[i][k] * b[k][j]; 147 } 148 result[i][j] = aggregation < 0 ? Long.MAX_VALUE : aggregation; 149 } 150 } 151 return result; 152 } 153 154 public long bruteForceProcess() { 155 int age = 0; 156 while (true) { 157 age++; 158 for (int i = 1; i < n; i++) { 159 basic[i] = basic[i - 1] + basic[i]; 160 if (basic[i] >= threshold) { 161 return age; 162 } 163 } 164 } 165 } 166 167 /** 168 * @author dalt 169 * @see java.lang.AutoCloseable 170 * @since java1.7 171 */ 172 static class AcmInputReader implements AutoCloseable { 173 private PushbackInputStream in; 174 175 /** 176 * 创建读取器 177 * 178 * @param input 输入流 179 */ 180 public AcmInputReader(InputStream input) { 181 in = new PushbackInputStream(new BufferedInputStream(input)); 182 } 183 184 @Override 185 public void close() throws IOException { 186 in.close(); 187 } 188 189 private int nextByte() throws IOException { 190 return in.read() & 0xff; 191 } 192 193 /** 194 * 如果下一个字节为b,则跳过该字节 195 * 196 * @param b 被跳过的字节值 197 * @throws IOException if 输入流读取错误 198 */ 199 public void skipByte(int b) throws IOException { 200 int c; 201 if ((c = nextByte()) != b) { 202 in.unread(c); 203 } 204 } 205 206 /** 207 * 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times} 208 * 209 * @param b 被跳过的字节值 210 * @param times 跳过次数,-1表示无穷 211 * @throws IOException if 输入流读取错误 212 */ 213 public void skipByte(int b, int times) throws IOException { 214 int c; 215 while ((c = nextByte()) == b && times > 0) { 216 times--; 217 } 218 if (c != b) { 219 in.unread(c); 220 } 221 } 222 223 /** 224 * 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。 225 * 226 * @param b 被跳过的字节值 227 * @param times 跳过次数,-1表示无穷 228 * @throws IOException if 输入流读取错误 229 */ 230 public void skipBlankAndByte(int b, int times) throws IOException { 231 int c; 232 skipBlank(); 233 while ((c = nextByte()) == b && times > 0) { 234 times--; 235 skipBlank(); 236 } 237 if (c != b) { 238 in.unread(c); 239 } 240 } 241 242 /** 243 * 读取下一块不含空白字符的字符块 244 * 245 * @return 下一块不含空白字符的字符块 246 * @throws IOException if 输入流读取错误 247 */ 248 public String nextBlock() throws IOException { 249 skipBlank(); 250 StringBuilder sb = new StringBuilder(); 251 int c = nextByte(); 252 while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) { 253 sb.append((char) c); 254 } 255 in.unread(c); 256 return sb.toString(); 257 } 258 259 /** 260 * 跳过输入流中后续空白字符 261 * 262 * @throws IOException if 输入流读取错误 263 */ 264 private void skipBlank() throws IOException { 265 int c; 266 while ((c = nextByte()) <= 32) ; 267 in.unread(c); 268 } 269 270 /** 271 * 读取下一个整数(可正可负),这里没有对溢出做判断 272 * 273 * @return 下一个整数值 274 * @throws IOException if 输入流读取错误 275 */ 276 public int nextInteger() throws IOException { 277 skipBlank(); 278 int value = 0; 279 boolean positive = true; 280 int c = nextByte(); 281 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 282 positive = c == ‘+‘; 283 } else { 284 value = ‘0‘ - c; 285 } 286 c = nextByte(); 287 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 288 value = (value << 3) + (value << 1) + ‘0‘ - c; 289 c = nextByte(); 290 } 291 292 in.unread(c); 293 return positive ? -value : value; 294 } 295 296 /** 297 * 判断是否到了文件结尾 298 * 299 * @return true如果到了文件结尾,否则false 300 * @throws IOException if 输入流读取错误 301 */ 302 public boolean isMeetEOF() throws IOException { 303 int c = nextByte(); 304 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 305 return true; 306 } 307 in.unread(c); 308 return false; 309 } 310 311 /** 312 * 判断是否在跳过空白字符后抵达文件结尾 313 * 314 * @return true如果到了文件结尾,否则false 315 * @throws IOException if 输入流读取错误 316 */ 317 public boolean isMeetBlankAndEOF() throws IOException { 318 skipBlank(); 319 int c = nextByte(); 320 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) { 321 return true; 322 } 323 in.unread(c); 324 return false; 325 } 326 327 /** 328 * 获取下一个用英文字母组成的单词 329 * 330 * @return 下一个用英文字母组成的单词 331 */ 332 public String nextWord() throws IOException { 333 StringBuilder sb = new StringBuilder(16); 334 skipBlank(); 335 int c; 336 while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) { 337 sb.append((char) c); 338 } 339 in.unread(c); 340 return sb.toString(); 341 } 342 343 /** 344 * 读取下一个长整数(可正可负),这里没有对溢出做判断 345 * 346 * @return 下一个长整数值 347 * @throws IOException if 输入流读取错误 348 */ 349 public long nextLong() throws IOException { 350 skipBlank(); 351 long value = 0; 352 boolean positive = true; 353 int c = nextByte(); 354 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 355 positive = c == ‘+‘; 356 } else { 357 value = ‘0‘ - c; 358 } 359 c = nextByte(); 360 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 361 value = (value << 3) + (value << 1) + ‘0‘ - c; 362 c = nextByte(); 363 } 364 in.unread(c); 365 return positive ? -value : value; 366 } 367 368 /** 369 * 读取下一个浮点数(可正可负),浮点数是近似值 370 * 371 * @return 下一个浮点数值 372 * @throws IOException if 输入流读取错误 373 */ 374 public float nextFloat() throws IOException { 375 return (float) nextDouble(); 376 } 377 378 /** 379 * 读取下一个浮点数(可正可负),浮点数是近似值 380 * 381 * @return 下一个浮点数值 382 * @throws IOException if 输入流读取错误 383 */ 384 public double nextDouble() throws IOException { 385 skipBlank(); 386 double value = 0; 387 boolean positive = true; 388 int c = nextByte(); 389 if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) { 390 positive = c == ‘+‘; 391 } else { 392 value = c - ‘0‘; 393 } 394 c = nextByte(); 395 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 396 value = value * 10.0 + c - ‘0‘; 397 c = nextByte(); 398 } 399 400 if (c == ‘.‘) { 401 double littlePart = 0; 402 double base = 1; 403 c = nextByte(); 404 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 405 littlePart = littlePart * 10.0 + c - ‘0‘; 406 base *= 10.0; 407 c = nextByte(); 408 } 409 value += littlePart / base; 410 } 411 in.unread(c); 412 return positive ? value : -value; 413 } 414 415 /** 416 * 读取下一个高精度数值 417 * 418 * @return 下一个高精度数值 419 * @throws IOException if 输入流读取错误 420 */ 421 public BigDecimal nextDecimal() throws IOException { 422 skipBlank(); 423 StringBuilder sb = new StringBuilder(); 424 sb.append((char) nextByte()); 425 int c = nextByte(); 426 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 427 sb.append((char) c); 428 c = nextByte(); 429 } 430 if (c == ‘.‘) { 431 sb.append(‘.‘); 432 c = nextByte(); 433 while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) { 434 sb.append((char) c); 435 c = nextByte(); 436 } 437 } 438 in.unread(c); 439 return new BigDecimal(sb.toString()); 440 } 441 442 private static class AsciiMarksLazyHolder { 443 public static final byte BLANK_MARK = 1; 444 public static final byte SIGN_MARK = 1 << 1; 445 public static final byte NUMERAL_MARK = 1 << 2; 446 public static final byte UPPERCASE_LETTER_MARK = 1 << 3; 447 public static final byte LOWERCASE_LETTER_MARK = 1 << 4; 448 public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK; 449 public static final byte EOF = 1 << 5; 450 public static byte[] asciiMarks = new byte[256]; 451 452 static { 453 for (int i = 0; i <= 32; i++) { 454 asciiMarks[i] = BLANK_MARK; 455 } 456 asciiMarks[‘+‘] = SIGN_MARK; 457 asciiMarks[‘-‘] = SIGN_MARK; 458 for (int i = ‘0‘; i <= ‘9‘; i++) { 459 asciiMarks[i] = NUMERAL_MARK; 460 } 461 for (int i = ‘a‘; i <= ‘z‘; i++) { 462 asciiMarks[i] = LOWERCASE_LETTER_MARK; 463 } 464 for (int i = ‘A‘; i <= ‘Z‘; i++) { 465 asciiMarks[i] = UPPERCASE_LETTER_MARK; 466 } 467 asciiMarks[0xff] = EOF; 468 } 469 } 470 } 471 }