前缀和算法
写在前面:前缀和算法是一种重要的预处理算法,能大大降低查询的时间复杂度。最简单的题目就是:给定n个数和m次询问,每次询问一段区间的和。
查找一个区间的和我们可以从加法转换为减法,查询从L到R区间数字的和,其实可以转换为前R之和减去前L-1项之和。如下所示:
例题引入:
P3406 海底高铁
题目背景
大东亚海底隧道连接着厦门、新北、博艾、那霸、鹿儿岛等城市,横穿东海,耗资 100010001000 亿博艾元,历时 15 年,于公元 2058 年建成。凭借该隧道,从厦门可以乘坐火车直达台湾、博艾和日本,全程只需要 444 个小时。
题目描述
该铁路经过 NNN 个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第 iii 段铁路连接了城市 iii 和城市 i+1(1≤i<N)i+1(1\leq i<N)i+1(1≤i<N)。如果搭乘的比较远,需要购买多张车票。第 iii 段铁路购买纸质单程票需要 AiA_iAi 博艾元。
虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了 IC 卡。对于第 iii 段铁路,需要花 CiC_iCi 博艾元的工本费购买一张 IC 卡,然后乘坐这段铁路一次就只要扣 Bi(Bi<Ai)B_i(B_i<A_i)Bi(Bi<Ai) 元。IC 卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第 iii 段铁路的 IC 卡,无法乘坐别的铁路的车。
Uim 现在需要出差,要去 MMM 个城市,从城市 P1P_1P1 出发分别按照 P1,P2,P3,⋯ ,PMP_1,P_2,P_3,\cdots,P_MP1,P2,P3,⋯,PM 的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。
现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。
输入格式
第一行两个整数,N,MN,MN,M。
接下来一行,MMM 个数字,表示 PiP_iPi。
接下来 N−1N-1N−1 行,表示第 iii 段铁路的 Ai,Bi,CiA_i,B_i,C_iAi,Bi,Ci。
输出格式
一个整数,表示最少花费
输入输出样例
输入 #1
9 10 3 1 4 1 5 9 2 6 5 3 200 100 50 300 299 100 500 200 500 345 234 123 100 50 100 600 100 1 450 400 80 2 1 10
输出 #1
6394
说明/提示
222 到 333 以及 888 到 999 买票,其余买卡。
对于 30%30%30% 数据 M=2M=2M=2。
对于另外 30%30%30% 数据 N≤1000,M≤1000N\leq1000,M\leq1000N≤1000,M≤1000。
对于 100%100%100% 的数据 M,N≤105,Ai,Bi,Ci≤105M,N\leq 105,A_i,B_i,C_i\le105M,N≤105,Ai,Bi,Ci≤105。
代码
package com.jiangxi;
import java.util.Scanner;
public class P3406 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
int arr[]= new int[N+2];
long prefix[]= new long[N+2];
int coust[][] = new int [N+2][3];
int ex=sc.nextInt(),curr =0;
for (int i = 2; i <= M; i++) {
curr=sc.nextInt();
if(curr>ex){
arr[ex]++;
arr[curr]--;
}else{
arr[ex]--;
arr[curr]++;
}
ex=curr;
}
for (int i = 1; i <N; i++) {
coust[i][0] = sc.nextInt();
coust[i][1] = sc.nextInt();
coust[i][2] = sc.nextInt();
}
long sum=0;
for (int i = 1; i < prefix.length; i++) {
sum+=arr[i];
prefix[i]=sum;
}
long count=0;
for (int i = 1; i <N; i++) {
count+=Math.min(prefix[i]*coust[i][0],coust[i][2]+coust[i][1]*prefix[i] );
}
System.out.println(count);
}
}
我们在一遍遍覆盖的方法肯定是行不通的,那么有什么办法能不用经过一段铁路就把每个城市都加一遍1吗?当然是有的。
大家不妨想一想,我们在马路上会有警示牌啥的,告诉你前面是弯路,前面容易发生交通事故,前方容易遇见自己的未来女友…
但是警示牌不会在那一整段路上都插满(每毫米插一个?),他也只是在你将要进入那个路段,给你一个警示,然后我们自己就会在那一整段路上都注意(有没有未来女友),然后我们开过那段路之后,你就能差不多知道那段路已经过了(到了基友家)。
比如路牌说前方急转弯,好,当接着100m都是直路这时候我们就知道开过了,我们就不会受刚才的路牌影响,而是被下一个路牌影响,比如直路上有个路牌说“前方直路,小心开车过快,导致翻车” 接下来你就会在那一整段直路上注意,而不需要那一整段路上都给你插满“前方直路,小心开车过快,导致翻车”这个标语(老司机从不翻车)。
而这道题也是一样的。为什么我们要给经过的每个城市都加1呢?我们就不能也设置一个路牌吗?告诉计算机,接下来的一段铁路被经过了几次,然后在结束的时候再告诉他,结束了,你等待下一个路牌的指示,让下一个路牌告诉你下一段经过了几次。
我想这就是这题的思想
由于我们是正着遍历的,所以当你从数字较大的城市到小的城市,数字大的减一,小的加1,这样我们经过小的城市的时候我们就知道接下来的那段铁路是被经过了多少次,而到了减一的城市之后接下来经过的铁路就不受刚才路牌的影响,因为原来加的被减了呀。
所以关键在于如何构建路牌,这是一维的构建方法,二维也有他的构建方法,而且对于二维而言,路牌的作用就更加显著了。
所以我们只需记录起止点,然后
关键代码:
for (int i = 2; i <= M; i++) {
curr=sc.nextInt();
if(curr>ex){
arr[ex]++;
arr[curr]--;
}else{
arr[ex]--;
arr[curr]++;
}
ex=curr;
}