SYT的水题选讲4
CF704B Ant Man 题解
大佬的题解都是\(O(n^2)\)的,蒟蒻来补充一篇\(O(n \log n)\)的题解。
参考了xht37的题解和TA201314的题解.
我们分开考虑某个数\(i\)有哪些4类贡献
- A 小->i \(A_i=x_i+a_i\)
- B 大->i \(B_i=-x_i+b_i\)
- C i->小 \(C_i=x_i+c_i\)
- D i->大 \(D_i=-x_i+d_i\)
天马行空的想,我们不管\(s,e\)作为开头和结尾的限制(或者说\(s=1,e=2\)),只是从小到大的插入这\(n\)个数(这显然对答案没有影响),考虑增量构造。
一次插入的\(i\)永远是当前编号最大的那个,因此答案一定会增加\(A_i+C_i\)的贡献(这是肯定的)。
然后我们考虑两种情况,若\(i\)插入了\(x\rightarrow y\)的空隙中。
-
\(x>y\) 此时\(x\)的贡献由\(C_x\)变为\(D_x\),可以认为答案\(+(D_x-C_x)\)。在此之后,\(x\)的后继一定大于\(x\),所以\(x\)与\(x\)的后继产生的贡献不再变化。
-
\(x<y\) 此时\(y\)的贡献由\(A_y\)变为了\(B_y\),可以认为答案\(+(B_y-A_y)\)。在此之后,\(y\)的前驱一定大于\(y\),所以\(y\)与\(y\)的前驱产生的贡献不再变化。
我们如果把所有对答案的贡献(也就是上文提到的\(+(D_x-C_x)\),\(+(B_y-A_y)\))用数据结构维护,我们发现每次操作可以被理解为如下三个步骤:
(1) 给答案加上一个\(A_i+C_i\)。
(2) 从数据结构取一个值加到答案上,然后把这个值删掉(这个值不可能再被使用了)。
(3) 向数据结构中添加两个新的值\(B_i-A_i,D_i-C_i\),表示新建出两个空隙可能增加的贡献。
我们希望答案最小,所以(2)中取出当前的最小值。显然这种数据结构小根堆。
考虑\(s,e\)的限制。我们根据增量构造法,令\(ans=calc(s,e)\),初始序列为\(s,e\)。
注意到我们并不能保证i是当前的最大值,也就是操作 (1) 不一定合法。我们有如下巧妙的做法:
-
当\(i<s\)时,我们把插入\(B_i-A_i\)提前在 (2) 操作之前。这样我们虽然不能保证\(A_i\)是合法的,但是如果我们从堆里取出了\(B_i-A_i\),相当于我们选了\(B_i\)作为初始答案,即\(i\)左侧可能连的是\(s\)(右侧是合法的,所以没有新增贡献产生)
-
当\(i<e\)时,我们把插入\(D_i-C_i\)提前在 (2) 操作之前。这样我们虽然不能保证\(C_i\)是合法的,但是如果我们从堆里取出了\(D_i-C_i\),相当于我们选了\(D_i\)作为初始答案,即\(i\)右侧可能连的是\(e\).(左侧是合法的,所以没有新增贡献产生)
-
当\(i=1\&s\ne1\&e \ne 1\),左右侧都有可能不合法,比较麻烦,所以我们提前强制插入1。
当然\(i=s\|i=e\)是,我们不必要执行所有操作(因为已经算过了),我们只需要分别向小根堆中添加\(D_s-C_s\)和\(B_e-A_e\),表示:在计算s的时候,我们默认是s->小,现在提供一种可能让s->小变为s->大。(e同理)
最后输出答案即可.所以这道题可以被加强到\(N\le10^6\)的(duliu)
#include <bits/stdc++.h>
using namespace std;
typedef long long int64;
const int N = 5005;
int n , s , e;
int64 x[N] , a[N] , b[N] , c[N] , d[N];
int64 calc(int i , int j) {
if(i < j) return x[j] - x[i] + d[i] + a[j];
else return x[i] - x[j] + c[i] + b[j];
}
template<typename _T>
inline void read(_T &x) {
x = 0 ; char c = getchar(); bool f = 0;
while(c < '0' || c > '9') f |= (c=='-') , c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + (c^48) , c = getchar();
if(f) x = -x;
}
void input(int64* t) { for(int i = 1 ; i <= n ; ++i) read(t[i]); }
priority_queue<int64 , vector<int64> , greater<int64> > heap;
int64 ans = 0;
void init() {
read(n) , read(s) , read(e);
input(x) , input(a) , input(b) , input(c) , input(d);
if(s == 1 || e == 1) ans = calc(s , e);
else ans = calc(s , 1) + calc(1 , e);
//注意特判1,因为1的左右两端(一端)都是比他小的数,所以得特判
}
int main() {
init();
for(int64 i = 2 ; i <= n ; ++i) {
int64 A = x[i] + a[i] , B = -x[i] + b[i] , C = x[i] + c[i] , D = -x[i] + d[i];
//A:小->i B:大->i C:i->小 D:i->大
if(i == s) //在计算s的时候,我们默认是s->小,现在提供一种可能让s->小变为s->大
heap.push(D-C);
else if(i == e)//跟上面那个分析差不多
heap.push(B-A);
else {
ans += A+C;//我们先默认它左右两边都是比他小的数
if(i < s) heap.push(B-A);
if(i < e) heap.push(D-C);
ans += heap.top() , heap.pop();
if(i > s) heap.push(B-A);
if(i > e) heap.push(D-C);
}
}
cout << ans << endl;
}