HDU - 6521 Party (SYSU校赛K题)(线段树)

题目链接

题意: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\n",ans);
29         }
30     }
31     return 0;
32 }
View Code

这个算法本身是没有问题的,交上去也能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\n",sum[1]-ans);
40             ans=sum[1];
41         }
42     }
43     return 0;
44 }
View Code

然后据说还有一种叫“吉司机线段树”的东西也能做?赶紧学了学(便乘),感觉对于区间取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\n",sum[1]-ans);
42             ans=sum[1];
43         }
44     }
45     return 0;
46 }
View Code

 

posted @ 2019-04-26 14:07  jrltx  阅读(427)  评论(0编辑  收藏  举报