CodeForces 371D. Vessels 题解

发篇题解证明我的博客园还活着......

前言

这道题的常规写法大概有:链表,线段树。

我做的时候什么也没想,就直接线段树干就完了.....因为我没有脑子......

但是实际上可以用链表做到复杂度 O(\(n\)) 级别。

具体做法

  • 1.线段树

线段树做法需要支持的操作为:

区间覆盖,单点修改,区间查询,单点查询。

这道题目的难点就在于怎么样维护水一直往下面流。那么我们可以想到二分来判断水会流满哪一些沙漏,然后对于这段区间实行区间覆盖,用一个前缀和(前缀和里面存的是容量的前缀和)) + 区间查询目前水量区间和来辅助二分。最后我们发现会剩下一点水无法注满下一个漏斗,然后采用单点修改,将水注入不能注满的沙漏即可即可。

对于询问就直接单点查值即可。

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
char buf[1 << 22],*p1 = buf,*p2 = buf;
#define mid ((L[x] + R[x]) >> 1)
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin),p1 == p2) ? EOF: *p1++)
inline int read() {
    int x = 0 , flag = 1;
    char ch = getchar();
    for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1;
    for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    return x * flag;
}

typedef long long LL;
const int MAXN = 2e5 + 50;
int n,m;
int A[MAXN],S[MAXN];//A存容量,S存容量的前缀和

struct SegmentTree {
    LL sum[MAXN << 2],laz[MAXN << 2],L[MAXN << 2],R[MAXN << 2];
    void build(int x,int l,int r) {
        sum[x] = laz[x] = 0;
        L[x] = l , R[x] = r;
        if(l == r) return ;
        build(x << 1 , l , mid);
        build(x << 1 | 1 , mid + 1 , r);
        return ;
    }

    void ad(int x,LL k) {
        laz[x] = 1;
        sum[x] = S[R[x]] - S[L[x] - 1];//全部注满
        return ;
    }

    void changepos(int x,int pos,int k) {
        if(pos > n) return ;
        if(L[x] == R[x] && L[x] == pos) {
            sum[x] += k; return;
        }
        pushdown(x);
        if(pos <= mid) changepos(x << 1 , pos , k);
        if(pos  > mid) changepos(x << 1 | 1 , pos , k);
        sum[x] = sum[x << 1] + sum[x << 1 | 1];
        return ;
    }

    void change(int x,int l,int r,LL k) {
        if(l > r) return ;
        if(L[x] >= l && R[x] <= r) {
            ad(x,k); return ;
        }
        if(l <= mid) change(x << 1 , l , r , k);
        if(r  > mid) change(x << 1 | 1 , l , r , k);
        sum[x] = sum[x << 1] + sum[x << 1 | 1];
        return ;
    }

    void pushdown(int x) {//下传标记
        if(laz[x] == 0) return ;
        ad(x << 1 , laz[x]);
        ad(x << 1 | 1 , laz[x]);laz[x] = 0;
        return ;
    }

    int GetVal(int x,int l,int r) {
        if( l > r) return 0;
        int s = 0;
        if(L[x] >= l && R[x] <= r) return sum[x];
        pushdown(x);
        if(l <= mid) s += GetVal(x << 1 , l , r);
        if(r  > mid) s += GetVal(x << 1 | 1 , l , r);
        return s;
    }
} T;

int check(int u,int val) {//二分找出可以注满的沙漏的区间右端点
    int s = u - 1;//注意这个地方初值要写成u-1
    for(int i = log2(n - u + 1) ; i >= 0 ; i --) {
        int C = s + (1 << i);
        if(C > n) continue;
        if(val >= (S[C] - S[u - 1] - T.GetVal(1,u,C))) s += (1 << i);//二分
    }
    return s;
}

signed main() {
    n = read();
    for(int i = 1 ; i <= n ; i ++) A[i] = read(),S[i] = S[i - 1] + A[i];
    m = read();
    T.build(1 , 1 , n + 1);
    while(m --) {
        int op = read(),u,x;
        if(op == 1) {
            u = read() , x = read();
            int l = u , r = check(u,x),val = x - (S[r] - S[l - 1] - T.GetVal(1,l,r));
            //val代表的是剩下的水
            T.change(1, l, r, 1);//区间覆盖
            T.changepos(1,r + 1,val);//单点修改
        }
        else {
            u = read();
            cout << T.GetVal(1,u,u) << endl;
        }
    }
    return 0;
}
  • 链表

大概思路:

链表里存的是下一个没有被装满的水桶。初始值即为 \(nxt[i] = i + 1\),当一个水桶被装满的时候,我们每次跳其\(nxt[]\) 即可,这样子跳下去,直到跳到水没了,然后再更新路径上的 \(nxt\) 数组。这个方法不难发现均摊下来是 O(\(n\)) 的。代码短了不止一点点,快了不止亿点点。

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
char buf[1 << 22],*p1 = buf,*p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 1 << 21 , stdin),p1 == p2) ? EOF: *p1++)
inline int read() {
    int x = 0 , flag = 1;
    char ch = getchar();
    for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1;
    for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0';
    return x * flag;
}

typedef long long LL;
const int MAXN = 2e5 + 50;
int n,m;
int A[MAXN],nxt[MAXN],N[MAXN];

signed main() {
    n = read();
    for(int i = 1 ; i <= n ; i ++) A[i] = read(),nxt[i] = i + 1;
    m = read();
    while(m --) {
        int op = read(),u,x;
        if(op == 1) {
            u = read() , x = read();
            int now = u;
            while(nxt[now]) {
                if(x + N[now] >= A[now]) x -= (A[now] - N[now]), N[now] = A[now];
                else {N[now] += x ; break;}
                now = nxt[now];
            }
            int t;
            while(u != now) t = u , u = nxt[u] , nxt[t] = now;
        }
        else {
            u = read();
            cout << N[u] << endl;
        }
    }
    return 0;
}

总结

以后一定要动脑子思考问题,不要一上来就打暴力,这个习惯不好......

posted @ 2021-02-09 20:21  MYCui  阅读(76)  评论(0编辑  收藏  举报