1 题意
有
n
n
n个人,分别标号
1
,
2
,
.
.
.
,
n
1,2,...,n
1,2,...,n,每次选择一个区间
[
l
,
r
]
[l,r]
[l,r],然后让这个区间里面的人举办一个
p
a
r
t
y
party
party,使得两两之间相互认识。问:每次举办
p
a
r
t
y
party
party之后产生了多少个新的关系。
链接:link。
2 思路
令
a
[
i
]
=
j
(
j
⩽
i
)
a[i]=j(j \leqslant i)
a[i]=j(j⩽i),表示
[
j
,
i
]
[j,i]
[j,i]区间内两两相互认识。
假设在
[
l
,
r
]
[l,r]
[l,r]区间内举办了一个
p
a
r
t
y
party
party,对于所有的
a
[
i
]
a[i]
a[i]:
- 如果 a [ i ] ⩽ l a[i] \leqslant l a[i]⩽l,则对于第 i i i个人来说,前面没有新认识的人。
- 如果 a [ i ] > l a[i] > l a[i]>l,则对于第 i i i个人来说,前面有 l − a [ i ] l-a[i] l−a[i]个新认识的人。
基于上述思想,可以用线段树维护 a [ i ] a[i] a[i]的值,并维护区间最大值,这样对于第一种情况来说就不用更新到叶子节点。
上述线段树是典型的势能线段树(大名鼎鼎的吉司机线段树),还可以继续做优化,这里暂不赘述。
2.1 时间复杂度分析
在最坏情况下,区间选择是这样的 [ r − 1 , r ] , [ r − 2 , r ] , . . . , [ 1 , r ] [r-1,r],[r-2,r],...,[1,r] [r−1,r],[r−2,r],...,[1,r],使得每次都要更新叶子节点,时间复杂度为 O ( ∑ i = 1 n ( i × l o g ( i ) ) ) \mathcal{O}( \sum_{i=1}^{n}(i \times log(i))) O(∑i=1n(i×log(i))),接近 O ( n 2 log ( n ) ) \mathcal{O}(n^{2} \log(n)) O(n2log(n))。
2.2 实现
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e5+5;
struct Node{
int val,mxv;
}node[N<<2];
int a[N];
int builtTree(int ind,int be,int en){
if(be==en) return node[ind].val=node[ind].mxv=a[be];
int mid=(be+en)>>1;
return node[ind].mxv=max(builtTree(ind<<1,be,mid),builtTree((ind<<1)+1,mid+1,en));
}
int query(int ind,int be,int en,int fbe,int fen,int val){
if(val>=node[ind].mxv) return 0;
int cnt=0;
if(be==en){
cnt+=node[ind].val-val;
node[ind].val=node[ind].mxv=val;
return cnt;
}
int mid=(be+en)>>1;
if(mid>=fen) cnt=query(ind<<1,be,mid,fbe,fen,val);
else if(mid<fbe) cnt=query((ind<<1)+1,mid+1,en,fbe,fen,val);
else cnt=query(ind<<1,be,mid,fbe,mid,val)+query((ind<<1)+1,mid+1,en,mid+1,fen,val);
node[ind].mxv=max(node[ind<<1].mxv,node[(ind<<1)+1].mxv);
return cnt;
}
int main(){
int n,m;
while(~scanf("%d %d",&n,&m)){
for(int i=1;i<=n;i++) a[i]=i;
builtTree(1,1,n);
while(m--){
int x,y;scanf("%d %d",&x,&y);
printf("%d\n",query(1,1,n,x,y,x));
}
}
}