2019中山大学程序设计竞赛(重现赛) hdu6521 Party · 线段树

题解

题意:1~n号参加m次派对,每次选一个区间的人参加,每次参加派对之后,区间内所有的人都会相互认识,问每次有多少对新朋友产生

当两人相互认识时,是一对朋友,所以相互认识可以看成左边的人与右边的人认识

这样每一个人的右边都有一个离自己最远的朋友,

假设参加新的派对的区间 [ l , r ] [l,r] [l,r] 里,有一部分人已经认识了,

每一个 i i i 的右边都有一个离自己最远的朋友,从这个朋友的右边开始到右端点 r r r 的这一段区间,都是这个 i i i 在这次派对上认识的新朋友,

i i i 与左边那些不认的人呢?

假设当前位置 a < b < c < i < d a<b<c<i<d a<b<c<i<d,如果 c c c i i i 认识,且 b b b d d d 认识,那么 b b b i i i 肯定认识,

假如在区间 [ a , d ] [a,d] [a,d] 中, a a a 只与 b b b 认识, c , i , d c,i,d c,i,d 相互认识,那么 a a a 即将认识的人的范围 [ c , d ] [c,d] [c,d] 中里就有 i i i

所以 i i i 左边那部分由左边的人负责统计

[ l , r ] [l,r] [l,r] 内的人都相互认识时,那么每个人都能认识到右端点 r r r

假设有一部分人还未和右边的人认识,

设最后一个还未能认识到右端点 r r r 的人的位置为 p o s pos pos,其认识的右边的最远的朋友的位置为 R [ p o s ] R[pos] R[pos]

其和右边未认识的人可以组出 r − R [ p o s ] r-R[pos] rR[pos] 对新朋友

枚举从 [ l , p o s ] [l,pos] [l,pos] 所有可以得到的答案( p o s pos pos 之后的所有人都能够认识到右端点 r r r

因此可以推出 [ l , r ] [l,r] [l,r] 内新认识的朋友的总数:
∑ i = l p o s r − R [ i ] \sum_{i=l}^{pos}r-R[i] i=lposrR[i]
化简一下就是
( p o s − l + 1 ) × r − ∑ i = l p o s R [ i ] (pos-l+1)\times r-\sum^{pos}_{i=l}R[i] (posl+1)×ri=lposR[i]

接着就是线段树一顿操作查询区间答案、修改每个人能认识到的右端点了

哦对了,hdu不要用cout输出,对的答案都能给你判wa


在这里插入图片描述


代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;

int n, m, K;

namespace segment_tree {//线段树板子
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
    int Max[N << 2];//Max[i] 表示位置为i的人能够认识到的右边最远的朋友的位置 就是之前说的R[]
    int lazy[N << 2];//懒标记用于向下更新子区间右端点最远距离
    ll sum[N << 2];//统计R[]之和 也可以说是 已经认识的朋友对数


    void pushup(int rt) {
        Max[rt] = max(Max[rt << 1], Max[rt << 1 | 1]);
        sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
    }

    void pushdown(int l, int r, int rt) {
        int mid = l + r >> 1;
        if (lazy[rt]) {
            //先把懒标记下放到子区间 能下放说明子区间的所有人都是认识Max[]的
            lazy[rt << 1] = max(lazy[rt << 1], lazy[rt]);
            lazy[rt << 1 | 1] = max(lazy[rt << 1 | 1], lazy[rt]);

            Max[rt << 1] = max(Max[rt << 1], lazy[rt]);
            Max[rt << 1 | 1] = max(Max[rt << 1 | 1], lazy[rt]);

            sum[rt << 1] = 1ll * (mid - l + 1) * Max[rt << 1];
            sum[rt << 1 | 1] = 1ll * (r - mid) * Max[rt << 1 | 1];

            lazy[rt] = 0;
        }

    }

    void build(int l, int r, int rt) {
        lazy[rt] = 0;
        if (l == r) {
            //初始化时 l号只认识自己
            sum[rt] = Max[rt] = l;
            return;
        }
        int mid = l + r >> 1;
        build(lson);
        build(rson);
        pushup(rt);
    }

    //将 [L,R] 所有人的Max[]全部改为pos
    void update(int L, int R, int pos, int l, int r, int rt) {
        if (L <= l && r <= R) {
            Max[rt] = max(Max[rt], pos);
            sum[rt] = 1ll * (r - l + 1) * Max[rt];
            // 更新时整个区间的右端点相同都是Max[rt](不一定是r)
            // sum求的是在这一段区间内有多少个Max[rt]
            lazy[rt] = Max[rt];
            return;
        }
        pushdown(l, r, rt);
        int mid = l + r >> 1;
        if (L <= mid) update(L, R, pos, lson);
        if (R > mid) update(L, R, pos, rson);
        pushup(rt);
    }

    int queryPos(int l, int r, int rt, int pos) {
        pushdown(l, r, rt);
        if (l == r) return Max[rt];//
        int mid = l + r >> 1;
        if (pos <= mid) return queryPos(lson, pos);
        else return queryPos(rson, pos);
    }

    //查询相互认识的有多少
    ll querySum(int L, int R, int l, int r, int rt) {
        if (L <= l && r <= R) {
            return sum[rt];
        }
        pushdown(l, r, rt);
        int mid = l + r >> 1;
        ll res = 0;
        if (L <= mid) res += querySum(L, R, lson);
        if (R > mid) res += querySum(L, R, rson);
        pushup(rt);
        return res;
    }
}
using namespace segment_tree;

int L, R;

int main() {
    ios::sync_with_stdio(0);

    while (cin >> n >> m) {
        build(1, n, 1);
        while (m--) {
            cin >> L >> R;

            //二分查找
            int l = 1, r = n, pos = 0;
            // 找到最右边的一个位置p
            // 使得在p之前的所有人认识的人的右端点小于r
            while (l <= r) {
                int mid = l + r >> 1;
                if (queryPos(1, n, 1, mid) < R) {
                    pos = mid;
                    l = mid + 1;
                } else r = mid - 1;
            }

            pos = min(R, pos);
            if (L <= pos) {
                printf("%lld\n", (1ll * (pos - L + 1) * R - querySum(L, pos, 1, n, 1)));
                update(L, pos, R, 1, n, 1);//将区间 [L,pos]内所有的点的右端点全部改为R
            } else puts("0");
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值