CS Academy Round#86 E.Growing Segment

题目链接

CsAcademy Round 86 E.Growing Segment

题目大意

在一根数轴上,你初始有一个长度为 \(len\) 的区间左端点在 \(0\) 的位置,有一系列点 \(X\),你需要挪动区间使其依次覆盖到 \(X\) 中的点,现在有 \(Q\) 次询问,每次给出 \(len\),求最少需要移动的距离。

\(1\leq|X|,Q\leq10^5\), \(0\leq len\leq10^9\), \(-10^9\leq X_i\leq 10^9\)

思路

我们直接观察样例:

输入(第一行 \(|X|\)\(Q\) ,第二行 \(X\) ,第三行询问的 \(len\)

9 6
2 -3 -1 1 2 3 5 3 7
0 1 2 3 4 5

输出:

21
16
11
10
9
8

具体点,我们把图画出来:

CS Academy Round#86 E.Growing Segment

显然,其中有一些冗余的部分计算答案的时候可以直接合并的,经过精简化:

CS Academy Round#86 E.Growing Segment

在草稿纸上,将 \(len\) 为不同值时的移动情况记录下来:

0 :  +2 -5 +8 -2 +4 
1 :  +1 -4 +7 -1 +3
2 :   0 -3 +6  0 +2
3 :   0 -3 +5  0 +2
4 :   0 -3 +4  0 +2
5 :   0 -3 +3  0 +2
6 :   0 -3 +2  0 +2
7 :   0 -3 +1  0 +2
8 :   0 -3  0  0 +2
9 :   0 -3  0  0 +1
10:   0 -3  0  0  0

结合这张表,可以发现这样的规律:

  • 每个方向的移动距离随着 \(len\) 的增加等速减小
  • 一个方向的移动是否会变化取决于前面移动的状态,跳过中间的所有 \(0\) 的地方,若前面那个非零的位置和当前位置同奇偶,那么当前移动就被 “冻结” 了,否则当前移动的距离会正常减小。

规律很好解释,第一条显然,第二条是两种移动是否拥有同方向端点导致的,比如上表中的第 \(3\) 个移动和第 \(5\) 个移动,它们在 \(len\in[2,8]\) 时端点都在右侧,分别是 \(F\)\(J\),端点相对位置不变,那么移动距离当然也就不变了.

然后我们就可以得到这题的做法了。

把询问中的 \(len\) 按从小到大排序,先预处理出最开始(即 \(len=0\))的移动情况,把它们丢进小根堆。用链表维护每一个移动的上一个非零移动是谁,\(frozen\) 记录该移动是否处于 “冻结” 状态,\(delta\) 记录当前有多少移动的距离是在减少中的。

我们处理一个 \(len\) 的时候将在此之前距离会归零的移动从小根堆里拿出来,若它处于 “冻结” 或不是最新的状态就continue ,否则把此移动归零之后,更新链表,根据奇偶性把后面的那个移动 “冻结” 或 “解冻”,“冻结” 的时候要记录一下目前移动距离还剩多少,“解冻” 时更新其归零的时间,然后插入到小根堆里。\(delta\) 实时更新,并根据时间维护移动距离总和。

时间复杂度:\(O(nlogn)\)\(n\)\(|X|\)

实现细节

  • 初始时区间左端点在 \(0\) 的位置,需将这个也视为一次移动,如果第一次移动是往左边走,那么该移动一开始就处于 ”冻结” 状态。
  • 注意相邻的几个移动同时归零的情况,处理不好的话链表的状态会出现问题。
  • 链表的右边是边界的时候不要更新 \(delta\)
  • 注意开 long long。

Code

54ms 的最优解 \(QAQ\)

其实大众主要的做法是维护点是否对答案有贡献,做法大同小异,如 atatmoir 的code,但是常数好像没这个小,不过本人的做法细节多是真的。

#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstdio>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 100100
#define ll long long
#define PLL pair<ll, ll>
#define fr first
#define sc second
using namespace std;

inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    while(ch < ‘0‘ || ch > ‘9‘){if(ch == ‘-‘) w = -1; ch = getchar();}
    while(ch >= ‘0‘ && ch <= ‘9‘) s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
    return s*w;
}

ll X[N], n, q;
ll a[N], cnt, ans[N];
PLL len[N];
priority_queue<PLL, vector<PLL>, greater<PLL> > change;
bool frozen[N];
struct node{
    ll pre, nxt;
}mov[N];

int main(){
    cin>>n>>q;
    rep(i,1,n) X[i] = read();
    rep(i,1,q) len[i].fr = read(), len[i].sc = i;
    sort(len+1, len+1+q);

    ll tot = 0;
    ll pos = 0, st = 0;
    rep(i,1,n){
        while(i < n && ((X[i+1]-X[i] >= 0)^(X[i]-pos >= 0)) == 0) i++;
        if(cnt == 0 && X[i]-pos < 0) cnt = st = 1, frozen[cnt+1] = true;
        a[++cnt] = abs(pos-X[i]), change.push({a[cnt], cnt}), tot += a[cnt];
        pos = X[i], mov[cnt] = {cnt-1, cnt+1};
    }

    ll delta = cnt-2*st, lst = 0;
    rep(i,1,q){
        while(!change.empty() && change.top().fr < len[i].fr){
	    PLL cur = change.top(); change.pop();
            ll id = cur.sc;
            if((frozen[id] && a[id]) || cur.fr != a[cur.sc]) continue;
            tot -= (cur.fr-lst) * delta;
            delta--, a[id] = 0;

            ll nxt = mov[id].nxt;
            if(nxt <= cnt) if((mov[nxt].pre^nxt)&1) delta--;
            mov[nxt].pre = mov[id].pre;
            mov[mov[id].pre].nxt = nxt;
            if(nxt <= cnt){
                if((mov[nxt].pre^nxt)&1){
		    delta++;
		    if(frozen[nxt]) a[nxt] += cur.fr, frozen[nxt] = false;
		    change.push({a[nxt], nxt});
		}
                else frozen[nxt] = true, a[nxt] -= cur.fr;
            }
            lst = cur.fr;
        }
        tot -= (len[i].fr-lst)*delta, lst = len[i].fr;
        ans[len[i].sc] = tot;
    }

    rep(i,1,q) printf("%lld\n", ans[i]);
    return 0;
}

教训:拍错还是要拍小数据!不少错解在大随机数据上的表现是可以相当优秀的。

CS Academy Round#86 E.Growing Segment

上一篇:win10系统许可证即将过期的解决方法


下一篇:C#事件总线