原题链接:https://codeforces.com/contest/311/problem/B

B. Cats Transport

题目描述

沿路有n(2n105)n(2\le n\le 10^5)座土堆,第i1i-1和第ii座土堆之间的距离为di(1di<104)d_i(1\le d_i \lt 10^4)米,有m(1m105)m(1\le m\le 10^5)只猫,第jj只猫在tj(0tj109)t_j(0\le t_j\le 10^9)时刻走到土堆hj(1hjn)h_j(1\le h_j\le n)上,然后在该土堆上等待,饲养员从第一个土堆出发,速度为11米/单位时间,按顺序从11nn走过所有土堆,如果tt时刻到了第jj座土堆,猫已经在这座土堆上了,则将这只猫带走,猫的等待时间为ttjt-t_j,共有p(1p100)p(1\le p \le 100)个饲养员,求将所有猫带走总等待时间最小是多少

示例

输入

1
2
3
4
5
6
7
8
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12

输出

1
3

说明

共有4座土堆,6只猫,2个饲养员;4座土堆的距离土堆00的距离可以记为[0,1,4,9][0,1,4,9],猫到各个土堆的时刻为[[0,10],[1,10],[12],[9]][[0,10],[1,10],[12],[9]]

第一个饲养员00时刻出发,到达44座土堆的时间分别是[0,1,4,9][0,1,4,9],带走33只猫,等待时间为00

剩余33只猫:[[10],[10],[12],[]][[10],[10],[12],[]],第22个饲养员1010时刻出发,到达时间为[10,11,14,19][10,11,14,19],带走剩余33只猫,等待时间为(1110)+(1412)=3(11-10)+(14-12)=3,即为最小等待时间

解决方案

将猫在各个土堆上的时间减去该土堆距离土堆00的距离,并从小到大排序,得到a=[0,0,0,8,9,10]a=[0,0,0,8,9,10],表示从土堆00触发的时刻需要大于等于该值,才能够接到对应的猫,假如出发的时刻为TT,则对应的等待时间为Ta[i]T-a[i];现在用数组aa的下标来代表对应的猫,可以观察到,如果出发时刻为a[i]a[i],则i\le i的所有猫都必定可以被接走,且早接走必定比晚接走等待时间短;因此可以得到如下的转移公式:(dp[p][i]dp[p][i]表示有pp个饲养员时,最小的等待时间,sum[i]sum[i]表示a[1]+a[2]+...+a[i]a[1]+a[2]+...+a[i])

dp[p][i]=min(dp[p1][j]+a[i](ij)(sum[i]sum[j])),j<idp[p][i]=min(dp[p-1][j]+a[i]*(i-j)-(sum[i]-sum[j])), j<i

dp[p][i]=min((j)a[i]+dp[p1][j]+sum[j])+a[i]isum[i],j<idp[p][i]=min((-j)*a[i]+dp[p-1][j]+sum[j])+a[i]*i-sum[i], j<i

朴素的时间复杂度为O(pmm)O(p*m*m),利用凸包技巧Convex Hull Trick,可以将时间复杂度优化到O(pm)O(p*m)

时间复杂度

O(pm)O(p*m)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
#include <bits/stdc++.h>

using namespace std;

template <typename T1, typename T2> istream &operator>>(istream &is, pair<T1, T2> &pa) { is >> pa.first >> pa.second; return is; }
template <typename T> istream &operator>>(istream &is, vector<T> &vec) { for (auto &v : vec) is >> v; return is; }
template <typename T1, typename T2> ostream &operator<<(ostream &os, const pair<T1, T2> &pa) { os << "(" << pa.first << "," << pa.second << ")"; return os; }
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) { os << "["; for (auto v : vec) os << v << ","; os << "]"; return os; }
template <typename T> ostream &operator<<(ostream &os, const deque<T> &vec) { os << "deq["; for (auto v : vec) os << v << ","; os << "]"; return os; }
template <typename T> ostream &operator<<(ostream &os, const set<T> &vec) { os << "{"; for (auto v : vec) os << v << ","; os << "}"; return os; }
template <typename T> ostream &operator<<(ostream &os, const multiset<T> &vec) { os << "{"; for (auto v : vec) os << v << ","; os << "}"; return os; }
template <typename T> ostream &operator<<(ostream &os, const unordered_set<T> &vec) { os << "{"; for (auto v : vec) os << v << ","; os << "}"; return os; }
template <typename T> ostream &operator<<(ostream &os, const unordered_multiset<T> &vec) { os << "{"; for (auto v : vec) os << v << ","; os << "}"; return os; }
template <typename TK, typename TV> ostream &operator<<(ostream &os, const unordered_map<TK, TV> &mp) { os << "{"; for (auto v : mp) os << v.first << "=>" << v.second << ","; os << "}"; return os; }
template <typename TK, typename TV> ostream &operator<<(ostream &os, const map<TK, TV> &mp) { os << "{"; for (auto v : mp) os << v.first << "=>" << v.second << ","; os << "}"; return os; }
template <typename T> void resize_array(vector<T> &vec, int len) { vec.resize(len); }
template <typename T, typename... Args> void resize_array(vector<T> &vec, int len, Args... args) { vec.resize(len); for (auto &v : vec) resize_array(v, args...); }
template <typename T1, typename T2> pair<T1, T2> operator+(const pair<T1, T2> &l, const pair<T1, T2> &r) { return make_pair(l.first + r.first, l.second + r.second); }
template <typename T1, typename T2> pair<T1, T2> operator-(const pair<T1, T2> &l, const pair<T1, T2> &r) { return make_pair(l.first - r.first, l.second - r.second); }
long long gcd(long long a, long long b) { return b ? gcd(b, a % b) : a; }
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x; }

#define rep(i, a, n) for (int i = a; i < (n); i++)
#define per(i, a, n) for (int i = (n)-1; i >= a; i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(), (x).end()
#define fi first
#define se second
#define sz(x) ((int)(x).size())
typedef vector<int> vi;
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef double db;
#if DEBUG
#define dbg(x) cerr << #x << " = " << (x) << " (L" << __LINE__ << ") " << __FILE__ << endl;
#else
#define dbg(x)
#endif

template<typename T>
struct Line {
T k, b;
// 集合中的直线按照斜率从小到大排序,该值表示本直线与下一条直线的交点的横坐标
mutable T intersect_x;
bool operator<(const Line<T>& o) const { return k < o.k; } // 用于按照斜率排序
bool operator<(T x) const { // 用于在Query时进行二分查找
return intersect_x < x;
} // 用于二分查找
T Eval(T x) const { return k * x + b; } // 求直线在x点的y值
T IntersectX(const Line<T>& line) const { // 求与直线line的交点的横坐标
assert(k != line.k); // 斜率不能相等,否则除0异常
return Div(line.b - b, k - line.k);
}
T Div(T a, T b) const {
if constexpr (std::is_integral<T>::value) {
// floored division,
// 因为两条直线之间的交点可能是小数,但是x是整数,因此小数部分永远不会取到
// 所以求直线交点时只需要记录<=交点横坐标值的最大整数即可
// 3/2 -> 1; -3/2 -> -2; 4/2 -> 2; -4/2 -> 2
return a / b - ((a ^ b) < 0 && a % b);
} else {
return a / b;
}
}
};

// IS_SORTED=1表示插入和查询是按照斜率排好序的顺序进行的,n条直线的时间复杂度为O(n)
// IS_SORTED=0表示插入和查询默认是无序的,内部使用multiset排序,n条直线的时间复杂度为O(n*logn)
template <typename T, int IS_SORTED = 0, int FIND_MIN = 0>
class LineContainer {
public:
void Add(T k, T b) {
if (FIND_MIN) Add(Line<T>{-k, -b, 0});
else Add(Line<T>{k, b, 0});
}
// 查找使得k*x+b最大的那条直线并返回最大的k*x+b
T Query(T x) {
// assert(!lines.empty());
if (lines.empty())
return FIND_MIN ? numeric_limits<T>::max() : numeric_limits<T>::lowest();
auto l = lines.lower_bound(x);
return FIND_MIN ? -(l->Eval(x)) : l->Eval(x);
}
size_t Size() {
return lines.size();
}
void Swap(LineContainer<T, IS_SORTED, FIND_MIN>& other) {
lines.swap(other.lines);
}
void Merge(const LineContainer<T, IS_SORTED, FIND_MIN>& other) {
for (auto line : other.lines) {
Add(line);
}
}
private:
void Add(const Line<T>& line) {
auto nxt = lines.insert(line), cur = nxt++, prev = cur;
// 判断插入的直线后面的直线是否需要删除,如果删除则重新求相邻直线的交点
while (Intersect(cur, nxt)) nxt = lines.erase(nxt);
// 判断插入的直线本身是否需要删除,删除的话重新求相邻直线的交点
if (prev != lines.begin() && Intersect(--prev, cur))
Intersect(prev, cur = lines.erase(cur));
// 判断插入的直线前面的那一条直线是否需要删除,删除的话重新求相邻直线的交点
while ((cur = prev) != lines.begin() &&
(--prev)->intersect_x >= cur->intersect_x)
Intersect(prev, lines.erase(cur));
}
// 求直线l1与l2的交点横坐标(向下取整),并判断是否需要删除直线l2
bool Intersect(typename multiset<Line<T>>::iterator l1, typename multiset<Line<T> >::iterator l2) {
if (l2 == lines.end()) {
l1->intersect_x = numeric_limits<T>::max();
return false;
}
if (l1->k == l2->k) {
l1->intersect_x = l1->b > l2->b ? numeric_limits<T>::max()
: numeric_limits<T>::lowest();
} else {
l1->intersect_x = l1->IntersectX(*l2);
}
return l1->intersect_x >= l2->intersect_x;
}
multiset<Line<T>, less<>> lines;
};

template <typename T, int FIND_MIN>
class LineContainer<T, 1, FIND_MIN> {
public:
void Add(T k, T b) {
if (FIND_MIN) Add(Line<T>{-k, -b, 0});
else Add(Line<T>{k, b, 0});
}
// 查找使得k*x+b最大的那条直线并返回最大的k*x+b
T Query(T x) {
// assert(!lines.empty());
if (lines.empty())
return FIND_MIN ? numeric_limits<T>::max() : numeric_limits<T>::lowest();
T ans;
#define QUERY_INCREASE
#ifdef QUERY_INCREASE
while (lines.size() >= 2 && lines[0].Eval(x) <= lines[1].Eval(x)) {
lines.pop_front();
}
ans = lines.front().Eval(x);
#elif defined(QUERY_DECREASE)
while (lines.size() >= 2 && lines[lines.size() - 1].Eval(x) <= lines[lines.size() - 2].Eval(x)) {
lines.pop_back();
}
ans = lines.back().Eval(x);
#else
auto l = lower_bound(lines.begin(), lines.end(), x);
ans = l->Eval(x);
#endif
return FIND_MIN ? -ans : ans;
}
size_t Size() {
return lines.size();
}
void Swap(LineContainer<T, 1, FIND_MIN>& other) {
lines.swap(other.lines);
}
private:
void Add(const Line<T>& line) {
if (lines.empty()) {
lines.emplace_back(line);
lines.back().intersect_x = numeric_limits<T>::max();
}
else {
#define ADD_INCREASE
#ifdef ADD_DECREASE
if (line.k == lines[0].k) {
if (line.b > lines[0].b) lines.pop_front();
else return;
}
while (lines.size() >= 2 && lines[0].IntersectX(line) >= lines[0].IntersectX(lines[1])) {
lines.pop_front();
}
lines.emplace_front(line);
lines[0].intersect_x = lines[0].IntersectX(lines[1]);
#elif defined(ADD_INCREASE)
if (line.k == lines.back().k) {
if (line.b > lines.back().b) lines.pop_back();
else return;
}
while (lines.size() >= 2 && lines[lines.size()-1].IntersectX(line) <= lines[lines.size()-1].IntersectX(lines[lines.size()-2])) {
lines.pop_back();
}
lines.emplace_back(line);
lines[sz(lines)-2].intersect_x = lines[sz(lines)-2].IntersectX(lines[sz(lines)-1]);
lines.back().intersect_x = numeric_limits<T>::max();
#endif
}
}
deque<Line<T>> lines;
};

class Solution {
public:
void Solve() {
int n, m, p;
while(cin>>n>>m>>p) {
vector<ll> d(n);
rep(i,1,n) cin>>d[i];
rep(i,1,n) d[i] += d[i-1];

vector<ll> a(m);
ll h, t;
rep(i,0,m) {
cin>>h>>t;
a[i] = t - d[h-1];
}
sort(all(a));
dbg(a);

vector<ll> sum(m);
sum[0] = a[0];
rep(i,1,m) sum[i] = sum[i-1]+a[i];

vector<vector<ll>> dp(p, vector<ll>(m));
rep(j,0,m) dp[0][j] = a[j] * (j+1) - sum[j];

rep(i,1,p) {
LineContainer<ll, 1, 1> lines;
lines.Add(-(i-1), dp[i-1][i-1] + sum[i-1]);
rep(j,i,m) {
dp[i][j] = lines.Query(a[j]) - sum[j] + a[j] * j;
if (j+1 < m) lines.Add(-j, dp[i-1][j] + sum[j]);
}
}
dbg(dp);
cout << dp[p-1].back() << endl;

// cout << fixed << setprecision(9) << ans << endl;
}
}
private:
};

// #define USACO 1
void set_io(const string &name = "") {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#if FILE_IO || USACO
if (!name.empty()) {
freopen((name + ".in").c_str(), "r", stdin);
freopen((name + ".out").c_str(), "w", stdout);
}
#endif
}

int main() {
#if USACO
set_io("time");
#else
set_io("tmp");
#endif
Solution().Solve();

return 0;
}