题意:n个人排成一列,一开始他们互不认识,每次选[l,r]上的人开party,使他们互相认识,求出每次party之后新互相认识的人的对数。
思路:把“互相认识”变成单向连边,只考虑左边的人对右边的贡献。对于每个人,他认识的人的区间必然是连续的,可以维护他认识的最右边的人R,这样更新操作相当于把[l,r]所有人的R值变成max(R,r),可以构造线段树维护每个区间中R的最小值mi,如果最小值大于等于R的话就不用更新了,直接退出,否则暴力修改每个点的值。
先上个假算法:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e5+10,inf=0x3f3f3f3f; 5 #define ls (u<<1) 6 #define rs (u<<1|1) 7 #define mid ((l+r)>>1) 8 int mi[N<<2],n,m; 9 ll ans; 10 void pu(int u) {mi[u]=min(mi[ls],mi[rs]);} 11 void build(int u=1,int l=1,int r=n) { 12 if(l==r) {mi[u]=l; return;} 13 build(ls,l,mid),build(rs,mid+1,r),pu(u); 14 } 15 void upd(int L,int R,int u=1,int l=1,int r=n) { 16 if(l>R||r<L||mi[u]>=R)return; 17 if(l==r) {ans+=R-mi[u],mi[u]=R; return;} 18 upd(L,R,ls,l,mid),upd(L,R,rs,mid+1,r),pu(u); 19 } 20 int main() { 21 while(scanf("%d%d",&n,&m)==2) { 22 build(); 23 while(m--) { 24 ans=0; 25 int l,r; 26 scanf("%d%d",&l,&r); 27 upd(l,r); 28 printf("%lld ",ans); 29 } 30 } 31 return 0; 32 }
这个算法本身是没有问题的,交上去也能AC,但会被一些极端的数据卡死,比如[1,1],[1,2],...,[1,n]这样的,会被卡成n^2,因此可以加一些优化。
由于每个人认识的最右边的人R的值是非递减的,即任意i>j,R[i]>=R[j],因此每次发生变化的区间必然是连续的,可以把单点修改换成区间修改,这样就不会被卡了。
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e5+10,inf=0x3f3f3f3f; 5 #define ls (u<<1) 6 #define rs (u<<1|1) 7 #define mid ((l+r)>>1) 8 int mx[N<<2],mi[N<<2],lz[N<<2],n,m; 9 ll sum[N<<2],ans; 10 void pu(int u) { 11 mi[u]=min(mi[ls],mi[rs]); 12 mx[u]=max(mx[ls],mx[rs]); 13 sum[u]=sum[ls]+sum[rs]; 14 } 15 void pd(int u,int l,int r) { 16 if(lz[u]) { 17 sum[ls]=(ll)lz[u]*(mid-l+1),sum[rs]=(ll)lz[u]*(r-mid); 18 mi[ls]=mi[rs]=mx[ls]=mx[rs]=lz[ls]=lz[rs]=lz[u],lz[u]=0; 19 } 20 } 21 void build(int u=1,int l=1,int r=n) { 22 lz[u]=0; 23 if(l==r) {sum[u]=mi[u]=mx[u]=l; return;} 24 build(ls,l,mid),build(rs,mid+1,r),pu(u); 25 } 26 void upd(int L,int R,int u=1,int l=1,int r=n) { 27 if(l>R||r<L||mi[u]>=R)return; 28 if(l>=L&&r<=R&&mx[u]<=R) {sum[u]=(ll)R*(r-l+1),mi[u]=mx[u]=lz[u]=R; return;} 29 pd(u,l,r); 30 upd(L,R,ls,l,mid),upd(L,R,rs,mid+1,r),pu(u); 31 } 32 int main() { 33 while(scanf("%d%d",&n,&m)==2) { 34 build(),ans=sum[1]; 35 while(m--) { 36 int l,r; 37 scanf("%d%d",&l,&r); 38 upd(l,r); 39 printf("%lld ",sum[1]-ans); 40 ans=sum[1]; 41 } 42 } 43 return 0; 44 }
然后据说还有一种叫“吉司机线段树”的东西也能做?赶紧学了学(便乘),感觉对于区间取max/min这类问题的处理强大的,普适性也比较高。
对于区间取max操作,其基本思想是维护区间和sum,区间最小值mi,区间次小值se以及区间最小值个数nmi。如果要对[l,r]上的所有数与x取max,那么分三种情况讨论即可:
1)若x<=mi,则修改操作无效,退出
2)若mi<x<se,则将mi改成x,(sum+=x-mi)*ni,其余不变,同时下放标记
3)若x>=se,则在左右区间递归进行下去
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int N=5e5+10,inf=0x3f3f3f3f; 5 #define ls (u<<1) 6 #define rs (u<<1|1) 7 #define mid ((l+r)>>1) 8 int mi[N<<2],nmi[N<<2],se[N<<2],lz[N<<2],n,m; 9 ll sum[N<<2],ans; 10 void pu(int u) { 11 sum[u]=sum[ls]+sum[rs]; 12 mi[u]=min(mi[ls],mi[rs]),se[u]=max(mi[ls],mi[rs]); 13 se[u]=se[u]==mi[u]?min(se[ls],se[rs]):min(se[u],min(se[ls],se[rs])); 14 nmi[u]=(mi[ls]==mi[u]?nmi[ls]:0)+(mi[rs]==mi[u]?nmi[rs]:0); 15 } 16 void change(int u,int x) {sum[u]+=(ll)nmi[u]*(x-mi[u]),mi[u]=lz[u]=x;} 17 void pd(int u) { 18 if(~lz[u]) { 19 if(mi[ls]<lz[u])change(ls,lz[u]); 20 if(mi[rs]<lz[u])change(rs,lz[u]); 21 lz[u]=-1; 22 } 23 } 24 void build(int u=1,int l=1,int r=n) { 25 lz[u]=-1; 26 if(l==r) {sum[u]=mi[u]=l,nmi[u]=1,se[u]=inf; return;} 27 build(ls,l,mid),build(rs,mid+1,r),pu(u); 28 } 29 void upd(int L,int R,int x,int u=1,int l=1,int r=n) { 30 if(l>R||r<L||x<=mi[u])return; 31 if(l>=L&&r<=R&&x<se[u]) {change(u,x); return;} 32 pd(u),upd(L,R,x,ls,l,mid),upd(L,R,x,rs,mid+1,r),pu(u); 33 } 34 int main() { 35 while(scanf("%d%d",&n,&m)==2) { 36 build(),ans=sum[1]; 37 while(m--) { 38 int l,r; 39 scanf("%d%d",&l,&r); 40 upd(l,r,r); 41 printf("%lld ",sum[1]-ans); 42 ans=sum[1]; 43 } 44 } 45 return 0; 46 }