题解
题意: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] r−R[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=l∑posr−R[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]
(pos−l+1)×r−i=l∑posR[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;
}