题目描述
该铁路经过N个城市,每个城市都有一个站。不过,由于各个城市之间不能协调好,于是乘车每经过两个相邻的城市之间(方向不限),必须单独购买这一小段的车票。第i段铁路连接了城市i和城市i+11≤i<N。如果搭乘的比较远,需要购买多张车票。第i段铁路购买纸质单程票需要Ai博艾元。
虽然一些事情没有协调好,各段铁路公司也为了方便乘客,推出了IC卡。对于第i段铁路,需要花Ci博艾元的工本费购买一张IC卡,然后乘坐这段铁路一次就只要扣Bi(Bi<Ai)元。IC卡可以提前购买,有钱就可以从网上买得到,而不需要亲自去对应的城市购买。工本费不能退,也不能购买车票。每张卡都可以充值任意数额。对于第i段铁路的IC卡,无法乘坐别的铁路的车。
某员工现在需要出差,要去M个城市,从城市P1出发分别按照P1,P2,P3…PM的顺序访问各个城市,可能会多次访问一个城市,且相邻访问的城市位置不一定相邻,而且不会是同一个城市。现在他希望知道,出差结束后,至少会花掉多少的钱,包括购买纸质车票、买卡和充值的总费用。
题目分析
我们可以这样分析:我们想知道最少花费,我们就需要比较买卡再买票的钱与直接买纸质票的钱,谁大谁小。于是我们可以用贪心思想取每段路程的花费最小,将局部最优解之和当成全局最优解,而通过对此题分析可以得出结论,局部最优解之和就是全局最优解。
在此基础上,我们继续进行反推。如何求局部解的和,此时我们自然想到了这种方法:每出差一趟,我们就把途径站的花费都加一遍,例如:某员工先从3出发去1,再去4,再去1,则我们需要将第一躺1-2段、2-3段的花费求和,第二趟的1-2段、2-3段、3-4段求和、第三趟3-4段、2-3段、1-2段求和,最后把所有的和加起来。
那么很显然,我们做了很多的重复计算。所以在此基础上,我们继续进行改进,我们可以通过减少运算的角度来想,即求出某员工出差过程中经过各个城市的次数,再将经过的次数与单次最少花费相乘得出总路程的最小花费。
在此基础上,我们再继续进行反推。如何求经过各个城市的次数?我们可以这样想:某员工从3到1再到4,这过程可以看成一种前缀和,即在3到1的过程包含1-2段和2-3段,经过1-2和2-3的次数加1,在1到4的过程中经过1-2段、2-3段、3-4段,经过1-2段、2-3段、3-4段的次数加1。所以经过各个城市的次数可以通过一组差分数组来求,而差分数组的和就是经过每个城市的次数。
所以,此题的思路就清晰了:输入数据进行存储、初始化相关的变量、通过先差分再求前缀和的方法求经过每个城市的次数、次数乘以最小花费得出全局最优解。
代码展示
#include<iostream>
#include <time.h>
using namespace std;
int n, m; //n个城市,去m个地方
typedef long long ll;
const int maxn = 1e5 + 10;
ll p[maxn] ,a[maxn],b[maxn],c[maxn],sum[maxn],d[maxn], money;
//p去的城市,
//a是纸票价,b是买卡后的价,c是买卡钱
//sum是前缀和
//d是计数数组
void counts(int a, int b)//求经过次数
{
d[a] += 1;
d[b] -= 1;
}
//求前缀和
void get_sum()
{
for (int i = 1; i <= n - 1; i++)
{
sum[i] = sum[i-1] + d[i];
}
}
int main()
{
clock_t start, finish;
//clock_t为CPU时钟计时单元数
//start = clock();//计时
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> p[i];//输入站台顺序
}
for (int i=2; i <= m; i++)
{
ll a = p[i - 1], b = p[i];
if (a > b)
{
swap(a, b);
}
//差分思想
counts(a, b);
}
get_sum();
for (int i = 1; i <= n - 1; i++)
{
cin >> a[i] >> b[i] >> c[i];
//求单次最小花费
money += min(a[i] * sum[i], b[i] * sum[i] + c[i]);
}
cout << money;
//finish = clock();
//clock()函数返回此时CPU时钟计时单元数
//cout << endl << "the time cost is:" << double(finish - start) / CLOCKS_PER_SEC << endl;
}