第四届CCF软件能力认证(CSP2015) 第五题(最小花费)题解

【问题描述】

  C国共有$n$个城市。有$n-1$条双向道路,每条道路连接两个城市,任意两个城市之间能互相到达。小R来到C国旅行,他共规划了$m$条旅行的路线, 第$i$条旅行路线的起点是$s_i$,终点是$t_i$。在旅行过程中,小R每行走一单位长度的路需要吃一单位的食物。C国的食物只能在各个城市中买到,而且不同城市的食物价格可能不同。
  然而,小R不希望在旅行中为了购买较低价的粮食而绕远路,因此他总会选择最近的路走。现在,请你计算小R规划的每条旅行路线的最小花费是多少。

【输入格式】

  第一行包含2个整数$n$和$m$。
  第二行包含$n$个整数。第$i$个整数$w_i$表示城市$i$的食物价格。
  接下来$n-1$行,每行包括3个整数$u, v, e$,表示城市$u$和城市$v$之间有一条长为$e$的双向道路。
  接下来$m$行,每行包含2个整数$s_i$和$t_i$,分别表示一条旅行路线的起点和终点。

【输出格式】

  输出$m$行,分别代表每一条旅行方案的最小花费。

【样例输入】

6 4
1 7 3 2 5 6
1 2 4
1 3 5
2 4 1
3 5 2
3 6 1
2 5
4 6
6 4
5 6

【样例输出】

35
16
26
13

【样例说明】

对于第一条路线,小R会经过2->1->3->5。其中在城市2处以7的价格购买4单位粮食,到城市1时全部吃完,并用1
的价格购买7单位粮食,然后到达终点。

【评测用例规模与约定】

  前10%的评测用例满足:$n, m ≤ 20, w_i ≤ 20$;
  前30%的评测用例满足:$n, m ≤ 200$;
  另有40%的评测用例满足:一个城市至多与其它两个城市相连。
  所有评测用例都满足:$1 ≤ n, m ≤ 10^5,1 ≤ w_i ≤ 10^6,1 ≤ e ≤ 10000$。

【题解】

首先注意到,一条路径的选择方案,一定是从一个点走到下一个比它便宜的点,这之间的食物都在这个点购买。

而这个信息不具有可加性,却具有可减性。

之前在网上搜到了一篇自称要维护两遍单调栈三个lct的博客,但是维护单调栈的最坏时间复杂度为$O(n^2)$,故无法通过所有的测试数据。

下面介绍一种基于点分治的做法。

假设当前分治中心为T,对于一个询问u->v,可以被拆成u->T,T->v,对于u->T的费用,我们可以在倍增数组上二分出u上面第一个比它便宜的位置,用这个位置的信息可以直接得出u的信息。

现在考虑T->v的费用,对于每一个询问,都附加了一个状态,表示之前便宜的费用c,我们需要在T->v的路径上找到第一个比它便宜的点设为x(这个可以通过dfs时维护一个前缀最小值数据来二分求得),这一段用的费用是dis(T, x) * c,剩下的部分就是从x向下走走到v的费用,我们可以通过dfs求出每个点到T的费用,之前已经提到过,维护的信息具有可见性,就可以$O(1)$的时间算出从一个点往下走的走到某个点的费用。

至此,问题在$O(n\log^2n)$的时间复杂度,$O(n\log n)$的空间复杂度内解决。

【代码】(滥用stl导致常数非常大)

  1 #include<bits/stdc++.h>
  2
  3 using namespace std;
  4
  5 typedef long long LL;
  6 typedef pair<int, int> pii;
  7 #define FI first
  8 #define SE second
  9 #define for_edge(u, it) for(vector<pii>::iterator it = G[u].begin(); it != G[u].end(); ++it)
 10
 11 const int N = 100000 + 10;
 12
 13 vector<pii> G[N];
 14 vector<int> Q[N], q1[N], q2[N];
 15 LL dis[N], disv[N];
 16 int cost[N], sz[N], maxsz[N], top[N], root;
 17 pii q_info[N];
 18 pair<LL, int> ans[N];
 19 bool centre[N];
 20
 21 #define v it->FI
 22 void get_size(int u, int fa) {
 23     maxsz[u] = 0, sz[u] = 1;
 24     for_edge(u, it) if(v != fa && !centre[v]) {
 25         get_size(v, u);
 26         sz[u] += sz[v];
 27         maxsz[u] = max(maxsz[u], sz[v]);
 28     }
 29 }
 30
 31 void get_root(int u, int fa, int r) {
 32     maxsz[u] = max(maxsz[u], sz[r] - sz[u]);
 33     if(maxsz[u] < maxsz[root]) root = u;
 34     for_edge(u, it) if(v != fa && !centre[v]) {
 35         get_root(v, u, r);
 36     }
 37 }
 38
 39 int anc[17][N], val[17][N];
 40
 41 int tot_time;
 42
 43 void get_top(int u, int fa, int pre) {
 44     anc[0][u] = fa, val[0][u] = cost[u];
 45     int tmp = clock();
 46     for(int i = 1; i < 17; i++) {
 47         val[i][u] = min(val[i-1][u], val[i-1][anc[i-1][u]]);
 48         anc[i][u] = anc[i-1][anc[i-1][u]];
 49     }
 50     tot_time += clock() - tmp;
 51
 52     top[u] = pre;
 53     for_edge(u, it) if(v != fa && !centre[v]) {
 54         dis[v] = dis[u] + it->SE, get_top(v, u, pre);
 55     }
 56 }
 57
 58 int pre[N];
 59
 60 int find(int u) {
 61     int cost_u = cost[u];
 62     //int tmp = clock();
 63     for(int i = 16; i >= 0; i--) {
 64         if(val[i][u] >= cost_u) u = anc[i][u];
 65     }
 66     //tot_time += clock() - tmp;
 67     return u;
 68 }
 69
 70 void calc_1(int u, int fa) {
 71     int anc = find(u);
 72
 73     if(anc) pre[u] = pre[anc];
 74     else anc = root, pre[u] = u; // be root when not exist
 75     disv[u] = (dis[u] - dis[anc]) * cost[u] + disv[anc];
 76
 77     for(unsigned i = 0; i < q1[u].size(); i++) {
 78         ans[q1[u][i]] = make_pair(disv[u], cost[pre[u]]);
 79     }
 80
 81     for_edge(u, it) if(v != fa && !centre[v]) {
 82         calc_1(v, u);
 83     }
 84
 85 }
 86
 87 void calc_2(int u, int fa, int fee) {
 88     static int val[N], id[N], tot;
 89     disv[u] = (dis[u] - dis[fa]) * fee + disv[fa];
 90     id[tot] = u, val[tot] = cost[u];
 91     if(tot++) val[tot-1] = min(val[tot-2], cost[u]);
 92
 93     id[tot] = u; // be u when not exist
 94     for(unsigned i = 0; i < q2[u].size(); i++) {
 95         int c = q2[u][i];
 96         int anc = id[lower_bound(val, val + tot, ans[c].SE, greater<int>()) - val];
 97         ans[c].FI += ans[c].SE * (dis[anc] - dis[root]) + disv[u] - disv[anc];
 98     }
 99
100     for_edge(u, it) if(v != fa && !centre[v]) {
101         calc_2(v, u, min(fee, cost[u]));
102     }
103     --tot;
104 }
105
106 void solve(int u) {
107     if(!Q[u].size()) return;
108
109     get_size(u, 0);
110     root = u, get_root(u, 0, u);
111     //cerr << sz[u] << ‘ ‘ << maxsz[root] << endl;
112
113     vector<int> vec_q;
114     vec_q.swap(Q[u]);
115
116     centre[u = root] = 1, top[u] = u, dis[u] = 0;
117     for(int i = 0; i < 17; i++) anc[i][u] = val[i][u] = 0;
118     val[0][u] = cost[u];
119     for_edge(u, it) if(!centre[v]) {
120         dis[v] = it->SE, get_top(v, u, v);
121     }
122
123     for(unsigned i = 0; i < vec_q.size(); i++) {
124         int c = vec_q[i], x = q_info[c].FI, y = q_info[c].SE;
125         if(top[x] == top[y]) Q[top[x]].push_back(c);
126         else q1[x].push_back(c), q2[y].push_back(c);
127     }
128
129
130     disv[u] = 0, pre[u] = u;
131     calc_1(u, 0);
132
133     disv[u] = 0;
134     calc_2(root, 0, cost[u]);
135
136     for(unsigned i = 0; i < vec_q.size(); i++) {
137         int c = vec_q[i], x = q_info[c].FI, y = q_info[c].SE;
138         q1[x].clear(), q2[y].clear();
139     }
140
141     for_edge(u, it) if(!centre[v]) {
142         solve(v);
143     }
144 }
145 #undef v
146
147 int main() {
148 #ifdef DEBUG
149     freopen("in.txt", "r", stdin);
150     freopen("out.txt", "w", stdout);
151     int start_time = clock();
152 #endif
153
154     int n, m; scanf("%d%d", &n, &m);
155     for(int i = 1; i <= n; i++) {
156         scanf("%d", cost + i);
157     }
158     for(int i = 1; i < n; i++) {
159         int u, v, w;
160         scanf("%d%d%d", &u, &v, &w);
161         G[u].push_back(pii(v, w));
162         G[v].push_back(pii(u, w));
163     }
164
165     for(int i = 0; i < m; i++) {
166         pii &cur = q_info[i];
167         scanf("%d%d", &cur.FI, &cur.SE);
168         if(cur.FI != cur.SE) Q[1].push_back(i);
169     }
170
171     solve(1);
172
173     for(int i = 0; i < m; i++) {
174         printf("%I64d\n", ans[i].FI);
175     }
176 #ifdef DEBUG
177     fprintf(stderr, "time used : %.5fs, %.5fs\n", (double) (clock() - start_time) / CLOCKS_PER_SEC, (double)tot_time / CLOCKS_PER_SEC);
178 #endif
179     return 0;
180 }

时间: 2024-10-10 02:54:43

第四届CCF软件能力认证(CSP2015) 第五题(最小花费)题解的相关文章

第四届CCF软件能力认证

1.图像旋转 问题描述 旋转是图像处理的基本操作,在这个问题中,你需要将一个图像逆时针旋转90度. 计算机中的图像表示可以用一个矩阵来表示,为了旋转一个图像,只需要将对应的矩阵旋转即可. 输入格式 输入的第一行包含两个整数n, m,分别表示图像矩阵的行数和列数. 接下来n行每行包含m个整数,表示输入的图像. 输出格式 输出m行,每行包含n个整数,表示原始矩阵逆时针旋转90度后的矩阵. 样例输入 2 31 5 33 2 4 样例输出 3 45 21 3 评测用例规模与约定 1 ≤ n, m ≤ 1

【实(dou)力(bi)首(mai)发(meng)】第四次CCF软件能力认证题解

这次的题总体上相对前三次偏简单.由于实力有限,就分析前四题.     试题编号:    201503-1 试题名称:    图像旋转 时间限制:    5.0s 内存限制:    256.0MB 问题描述:    问题描述 旋转是图像处理的基本操作,在这个问题中,你需要将一个图像逆时针旋转90度. 计算机中的图像表示可以用一个矩阵来表示,为了旋转一个图像,只需要将对应的矩阵旋转即可. 输入格式 输入的第一行包含两个整数n, m,分别表示图像矩阵的行数和列数. 接下来n行每行包含m个整数,表示输入

第一届CCF软件能力认证

1.相反数 问题描述 有 N 个非零且各不相同的整数.请你编一个程序求出它们中有多少对相反数(a 和 -a 为一对相反数). 输入格式 第一行包含一个正整数 N.(1 ≤ N ≤ 500). 第二行为 N 个用单个空格隔开的非零整数,每个数的绝对值不超过1000,保证这些整数各不相同. 输出格式 只输出一个整数,即这 N 个数中包含多少对相反数. 样例输入 51 2 3 -1 -2 样例输出 2 1 # include <iostream> 2 # include <cstdio>

第二届CCF软件能力认证

1. 相邻数对 问题描述 给定n个不同的整数,问这些数中有多少对整数,它们的值正好相差1. 输入格式 输入的第一行包含一个整数n,表示给定整数的个数. 第二行包含所给定的n个整数. 输出格式 输出一个整数,表示值正好相差1的数对的个数. 样例输入 610 2 6 3 7 8 样例输出 3 样例说明 值正好相差1的数对包括(2, 3), (6, 7), (7, 8). 评测用例规模与约定 1<=n<=1000,给定的整数为不超过10000的非负整数. 1 include <iostream

第三届CCF软件能力认证

1.门禁系统 问题描述 涛涛最近要负责图书馆的管理工作,需要记录下每天读者的到访情况.每位读者有一个编号,每条记录用读者的编号来表示.给出读者的来访记录,请问每一条记录中的读者是第几次出现. 输入格式 输入的第一行包含一个整数n,表示涛涛的记录条数. 第二行包含n个整数,依次表示涛涛的记录中每位读者的编号. 输出格式 输出一行,包含n个整数,由空格分隔,依次表示每条记录中的读者编号是第几次出现. 样例输入 51 2 1 1 3 样例输出 1 1 2 3 1 评测用例规模与约定 1≤n≤1,000

软件能力认证题---拼图(状态压缩DP+矩阵快速幂)

题意: 给定n*m的棋盘(1<=N<=10^15, 1<=M<=7),用L型骨牌(田字型任意去掉一个口)完全覆盖它,问有多少种解. 思路:m的范围只有1<=M<=7,显然状压DP.但是N的最大值到10^15,只能用快速幂了. 状态表示:0代表此处留空,1代表此处被填满.01序列压缩成一个int型来表示一行的填放情况.(例如:状态为4,则代表100,即第一列填满,第二第列三空) 边界条件: 其中, t = 2^M  代表将前i-1行填满,且第i行放置了状态s时的总方案数.

【基础练习】【贪心】【递推】NOIP2013提高组第五题 积木大赛题解

还是先把题目放上吧: 春春幼儿园举办了一年一度的"积木大赛".今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第n块积木的最终高度需要是hi. 在搭建开始之前,没有任何积木(可以看成n块高度为 0 的积木).接下来每次操作,小朋友们可以选择一段连续区间[L,R],然后将第L块到第R块之间(含第 L 块和第 R 块)所有积木的高度分别增加1. 小M是个聪明的小朋友,她很快想出了建造大厦的最佳策略,使得建造所需的操作次数最少.但她不是一个勤于动手的孩子,所以想

poj1985&amp;&amp;第四次CCF软件认证第4题 求树的直径

Cow Marathon Time Limit: 2000MS   Memory Limit: 30000K Total Submissions: 4216   Accepted: 2137 Case Time Limit: 1000MS Description After hearing about the epidemic of obesity in the USA, Farmer John wants his cows to get more exercise, so he has com

第四届CCF大数据学术会议征文通知

第四届CCF大数据学术会议征文通知 2016年10月,兰州 近几年,大数据是各界高度关注积极布局的热点方向.2015年8月,国务院发表<促进大数据发展行动纲要>,正式将大数据提升为国家战略,旨在全面推进我国大数据的发展和应用,加快建设数据强国.现如今大数据不但已成为全球IT行业最强劲的发展动力,而且正在引起各行各业的业务变革与产业升级.因此,为了探讨大数据相关领域所面临的挑战,共享各类创新思想,反映中国大数据技术的最新研究进展,交流大数据的应用现状和研发经验,继2013-2015成功召开了三届