codeforces:Prefix Sums分析和实现
题目大意:
给出一个函数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^3*log2(k)),而判断M*A[0]中是否有达到k的元素这一过程的时间复杂度可以不考虑(因为是小头)。故整个二分查找法的时间复杂度为O(log2(k))*O(n^3*log2(k))=O(n^3*(log2(k))^2),这在已知n<=10的前提下是可以接受的。
最后给出JAVA代码:
package cn.dalt.codeforces;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.math.BigDecimal;
import java.util.Map;
import java.util.TreeMap;
/**
* Created by dalt on 2017/9/10.
*/
public class BruteForcePrefixSums {
int n;
long threshold;
long[] basic;
public static void main(String[] args) throws Exception {
BruteForcePrefixSums solution = new BruteForcePrefixSums();
solution.init();
long result = solution.solve();
System.out.println(result);
}
public void init() throws Exception {
AcmInputReader input = new AcmInputReader(System.in);
n = input.nextInteger();
threshold = input.nextLong();
basic = new long[n];
for (int i = 0; i < n; i++) {
basic[i] = input.nextLong();
}
}
public long solve() {
//Remove prefix blank
{
int firstNotZero = 0;
while (basic[firstNotZero] == 0) {
firstNotZero++;
}
long[] tmp = new long[n - firstNotZero];
System.arraycopy(basic, firstNotZero, tmp, 0, tmp.length);
n = tmp.length;
basic = tmp;
}
//Test whether A0 satisfy the threshold
for (long value : basic) {
if (value >= threshold) {
return 0;
}
}
//n is more than 18, so brute force
if (n >= 10) {
return bruteForceProcess();
}
//Try fast matrix
else {
return binarySearch(basic);
}
}
public long binarySearch(long[] vector) {
long[][] a0 = new long[vector.length][vector.length];
for (int i = 0, bound = vector.length; i < bound; i++) {
for (int j = 0; j <= i; j++) {
a0[i][j] = 1;
}
}
TreeMap<Long, long[][]> cache = new TreeMap<>();
cache.put(1L, a0);
//Find lower bound and upper bound
long lowerAge = 0;
long[][] lowerMat = null;
long upperAge = 1;
long[][] upperMat = a0;
while (!contain(upperMat, vector)) {
lowerMat = upperMat;
lowerAge = upperAge;
cache.put(lowerAge, lowerMat);
upperMat = multiply(upperMat, upperMat);
upperAge *= 2;
}
//Binary search part
while (lowerAge < upperAge) {
long halfAge = (lowerAge + upperAge + 1) / 2;
//Calculate a0^half in fast way
Map.Entry<Long, long[][]> entry = cache.floorEntry(halfAge);
long remain = halfAge - entry.getKey();
long[][] halfMat = entry.getValue();
while (remain > 0) {
entry = cache.floorEntry(remain);
remain = remain - entry.getKey();
halfMat = multiply(halfMat, entry.getValue());
}
if (contain(halfMat, vector)) {
upperAge = halfAge - 1;
upperMat = halfMat;
} else {
lowerAge = halfAge;
lowerMat = halfMat;
}
}
return lowerAge + 1;
}
public boolean contain(long[][] mat, long[] vector) {
long[] result = new long[vector.length];
int row = mat.length;
long max = 0;
int col = mat[0].length;
for (int i = 0; i < row; i++) {
long aggregation = 0;
for (int j = 0; j < col; j++) {
long value = mat[i][j] * vector[j];
if (value >= threshold) {
return true;
}
aggregation += mat[i][j] * vector[j];
}
if (aggregation < 0 || aggregation >= threshold) {
return true;
}
}
return false;
}
public long[][] multiply(long[][] a, long[][] b) {
long[][] result = new long[a.length][b[0].length];
int row = a.length;
int col = b[0].length;
int mid = b.length;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
long aggregation = 0;
for (int k = 0; k < mid; k++) {
aggregation += a[i][k] * b[k][j];
}
result[i][j] = aggregation < 0 ? Long.MAX_VALUE : aggregation;
}
}
return result;
}
public long bruteForceProcess() {
int age = 0;
while (true) {
age++;
for (int i = 1; i < n; i++) {
basic[i] = basic[i - 1] + basic[i];
if (basic[i] >= threshold) {
return age;
}
}
}
}
/**
* @author dalt
* @see java.lang.AutoCloseable
* @since java1.7
*/
static class AcmInputReader implements AutoCloseable {
private PushbackInputStream in;
/**
* 创建读取器
*
* @param input 输入流
*/
public AcmInputReader(InputStream input) {
in = new PushbackInputStream(new BufferedInputStream(input));
}
@Override
public void close() throws IOException {
in.close();
}
private int nextByte() throws IOException {
return in.read() & 0xff;
}
/**
* 如果下一个字节为b,则跳过该字节
*
* @param b 被跳过的字节值
* @throws IOException if 输入流读取错误
*/
public void skipByte(int b) throws IOException {
int c;
if ((c = nextByte()) != b) {
in.unread(c);
}
}
/**
* 如果后续k个字节均为b,则跳过k个字节。这里{@literal k<times}
*
* @param b 被跳过的字节值
* @param times 跳过次数,-1表示无穷
* @throws IOException if 输入流读取错误
*/
public void skipByte(int b, int times) throws IOException {
int c;
while ((c = nextByte()) == b && times > 0) {
times--;
}
if (c != b) {
in.unread(c);
}
}
/**
* 类似于{@link #skipByte(int, int)}, 但是会跳过中间出现的空白字符。
*
* @param b 被跳过的字节值
* @param times 跳过次数,-1表示无穷
* @throws IOException if 输入流读取错误
*/
public void skipBlankAndByte(int b, int times) throws IOException {
int c;
skipBlank();
while ((c = nextByte()) == b && times > 0) {
times--;
skipBlank();
}
if (c != b) {
in.unread(c);
}
}
/**
* 读取下一块不含空白字符的字符块
*
* @return 下一块不含空白字符的字符块
* @throws IOException if 输入流读取错误
*/
public String nextBlock() throws IOException {
skipBlank();
StringBuilder sb = new StringBuilder();
int c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c = nextByte()] != AsciiMarksLazyHolder.BLANK_MARK) {
sb.append((char) c);
}
in.unread(c);
return sb.toString();
}
/**
* 跳过输入流中后续空白字符
*
* @throws IOException if 输入流读取错误
*/
private void skipBlank() throws IOException {
int c;
while ((c = nextByte()) <= 32) ;
in.unread(c);
}
/**
* 读取下一个整数(可正可负),这里没有对溢出做判断
*
* @return 下一个整数值
* @throws IOException if 输入流读取错误
*/
public int nextInteger() throws IOException {
skipBlank();
int value = 0;
boolean positive = true;
int c = nextByte();
if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
positive = c == '+';
} else {
value = '0' - c;
}
c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
value = (value << 3) + (value << 1) + '0' - c;
c = nextByte();
}
in.unread(c);
return positive ? -value : value;
}
/**
* 判断是否到了文件结尾
*
* @return true如果到了文件结尾,否则false
* @throws IOException if 输入流读取错误
*/
public boolean isMeetEOF() throws IOException {
int c = nextByte();
if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
return true;
}
in.unread(c);
return false;
}
/**
* 判断是否在跳过空白字符后抵达文件结尾
*
* @return true如果到了文件结尾,否则false
* @throws IOException if 输入流读取错误
*/
public boolean isMeetBlankAndEOF() throws IOException {
skipBlank();
int c = nextByte();
if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.EOF) {
return true;
}
in.unread(c);
return false;
}
/**
* 获取下一个用英文字母组成的单词
*
* @return 下一个用英文字母组成的单词
*/
public String nextWord() throws IOException {
StringBuilder sb = new StringBuilder(16);
skipBlank();
int c;
while ((AsciiMarksLazyHolder.asciiMarks[(c = nextByte())] & AsciiMarksLazyHolder.LETTER_MARK) != 0) {
sb.append((char) c);
}
in.unread(c);
return sb.toString();
}
/**
* 读取下一个长整数(可正可负),这里没有对溢出做判断
*
* @return 下一个长整数值
* @throws IOException if 输入流读取错误
*/
public long nextLong() throws IOException {
skipBlank();
long value = 0;
boolean positive = true;
int c = nextByte();
if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
positive = c == '+';
} else {
value = '0' - c;
}
c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
value = (value << 3) + (value << 1) + '0' - c;
c = nextByte();
}
in.unread(c);
return positive ? -value : value;
}
/**
* 读取下一个浮点数(可正可负),浮点数是近似值
*
* @return 下一个浮点数值
* @throws IOException if 输入流读取错误
*/
public float nextFloat() throws IOException {
return (float) nextDouble();
}
/**
* 读取下一个浮点数(可正可负),浮点数是近似值
*
* @return 下一个浮点数值
* @throws IOException if 输入流读取错误
*/
public double nextDouble() throws IOException {
skipBlank();
double value = 0;
boolean positive = true;
int c = nextByte();
if (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.SIGN_MARK) {
positive = c == '+';
} else {
value = c - '0';
}
c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
value = value * 10.0 + c - '0';
c = nextByte();
}
if (c == '.') {
double littlePart = 0;
double base = 1;
c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
littlePart = littlePart * 10.0 + c - '0';
base *= 10.0;
c = nextByte();
}
value += littlePart / base;
}
in.unread(c);
return positive ? value : -value;
}
/**
* 读取下一个高精度数值
*
* @return 下一个高精度数值
* @throws IOException if 输入流读取错误
*/
public BigDecimal nextDecimal() throws IOException {
skipBlank();
StringBuilder sb = new StringBuilder();
sb.append((char) nextByte());
int c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
sb.append((char) c);
c = nextByte();
}
if (c == '.') {
sb.append('.');
c = nextByte();
while (AsciiMarksLazyHolder.asciiMarks[c] == AsciiMarksLazyHolder.NUMERAL_MARK) {
sb.append((char) c);
c = nextByte();
}
}
in.unread(c);
return new BigDecimal(sb.toString());
}
private static class AsciiMarksLazyHolder {
public static final byte BLANK_MARK = 1;
public static final byte SIGN_MARK = 1 << 1;
public static final byte NUMERAL_MARK = 1 << 2;
public static final byte UPPERCASE_LETTER_MARK = 1 << 3;
public static final byte LOWERCASE_LETTER_MARK = 1 << 4;
public static final byte LETTER_MARK = UPPERCASE_LETTER_MARK | LOWERCASE_LETTER_MARK;
public static final byte EOF = 1 << 5;
public static byte[] asciiMarks = new byte[256];
static {
for (int i = 0; i <= 32; i++) {
asciiMarks[i] = BLANK_MARK;
}
asciiMarks['+'] = SIGN_MARK;
asciiMarks['-'] = SIGN_MARK;
for (int i = '0'; i <= '9'; i++) {
asciiMarks[i] = NUMERAL_MARK;
}
for (int i = 'a'; i <= 'z'; i++) {
asciiMarks[i] = LOWERCASE_LETTER_MARK;
}
for (int i = 'A'; i <= 'Z'; i++) {
asciiMarks[i] = UPPERCASE_LETTER_MARK;
}
asciiMarks[0xff] = EOF;
}
}
}
}
codeforces:Prefix Sums分析和实现的更多相关文章
- CodeForces 837F - Prefix Sums | Educational Codeforces Round 26
按tutorial打的我血崩,死活挂第四组- - 思路来自FXXL /* CodeForces 837F - Prefix Sums [ 二分,组合数 ] | Educational Codeforc ...
- Educational Codeforces Round 26 [ D. Round Subset ] [ E. Vasya's Function ] [ F. Prefix Sums ]
PROBLEM D - Round Subset 题 OvO http://codeforces.com/contest/837/problem/D 837D 解 DP, dp[i][j]代表已经选择 ...
- Codeforces 837F Prefix Sums
Prefix Sums 在 n >= 4时候直接暴力. n <= 4的时候二分加矩阵快速幂去check #include<bits/stdc++.h> #define LL l ...
- CodeForces 1204E"Natasha, Sasha and the Prefix Sums"(动态规划 or 组合数学--卡特兰数的应用)
传送门 •参考资料 [1]:CF1204E Natasha, Sasha and the Prefix Sums(动态规划+组合数) •题意 由 n 个 1 和 m 个 -1 组成的 $C_{n+m} ...
- 【题解】【数组】【Prefix Sums】【Codility】Genomic Range Query
A non-empty zero-indexed string S is given. String S consists of N characters from the set of upper- ...
- 【题解】【数组】【Prefix Sums】【Codility】Passing Cars
A non-empty zero-indexed array A consisting of N integers is given. The consecutive elements of arra ...
- CF1303G Sum of Prefix Sums
点分治+李超树 因为题目要求的是树上所有路径,所以用点分治维护 因为在点分治的过程中相当于将树上经过当前$root$的一条路径分成了两段 那么先考虑如何计算两个数组合并后的答案 记数组$a$,$b$, ...
- Educational Codeforces Round 26 F. Prefix Sums 二分,组合数
题目链接:http://codeforces.com/contest/837/problem/F 题意:如题QAQ 解法:参考题解博客:http://www.cnblogs.com/FxxL/p/72 ...
- Codeforces 1303G - Sum of Prefix Sums(李超线段树+点分治)
Codeforces 题面传送门 & 洛谷题面传送门 个人感觉这题称不上毒瘤. 首先看到选一条路径之类的字眼可以轻松想到点分治,也就是我们每次取原树的重心 \(r\) 并将路径分为经过重心和不 ...
随机推荐
- Beta阶段贡献分配规则
作业要求[https://edu.cnblogs.com/campus/nenu/2018fall/homework/2382] 在新成员加入后,我们经过讨论,决定沿用alpha阶段贡献分分配规则. ...
- 事务的隔离级别以及oracle中的锁
事务的概念及特性 事务,一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit). 例如:在关系数据库中,一个事务可以是一条SQL语句,一组SQL语 ...
- springboot项目配置拦截器,进行登陆等拦截
新建拦截类: public class LoginInterceptor implements HandlerInterceptor{ private static Log logger = LogF ...
- TypeScript学习笔记(八) - 声明文件
本篇将介绍TypeScript的声明文件,并简单演示一下如何编写和使用声明文件.本篇也是这个系列的最后一篇. 一.声明文件简介 TypeScript作为JavaScript的超集,在开发过程中不可避免 ...
- Spring boot启动原理
1.入口类 /** * springboot应用的启动入口 */ @RestController @SpringBootApplication public class SampleApplicati ...
- cratedb 基本试用
安装 docker run -d -p 4200:4200 crate UI访问 http://localhost:4200/#!/ 创建数据 tweets 是默认导入的,点击帮助导航可以操作 登陆 ...
- 输入和输出(read,recv,recvmsg...和write,writev,writemsg)
每一个TCP套接口有一个发送缓冲区,可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小. 应用进程调用 write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区.如果套接口的发送缓 ...
- dig命令使用大全
https://www.cnblogs.com/daxian2012/archive/2013/01/10/2854126.html 译者序:可以这样说,翻译本篇文档的过程就是我重新学习DNS的过程, ...
- brave-zipkin的日志源码分析
其实在zipkin的日志里面作为发送端日志两个,sr,ss,这个日志是servlet产生的:接收端日志是四个,分别是cr,sr,ss,cs:cr和cs分别是上游的日志的信息:ss和sr是接收端输出的日 ...
- docker基于commit命令创建支持ssh服务的镜像
以centos为基础,目的使用ssh服务远程连接docker容器. 环境:宿主机centos7(宿主机ip地址为192.168.164.130),直接搜索docker的centos镜像,下载最新版本. ...