2024.10.29

First Post:

Last Update:

Word Count:
3.4k

Read Time:
18 min

[蓝桥杯 2021 国 AB] 翻转括号序列

合法括号序列常见翻译:

把左括号当成 1,右括号当成 -1.

  1. 序列和为 0.
  2. 所有前缀的和 (转化为最小前缀和等于 0)

可以发现,最小前缀和这东西是有一点单调性的(在左端点固定时,最小前缀和关于右端点单调不增),所以可以考虑二分。

线段树上维护:

  1. 最小前缀和
  2. 所有最小前缀和对应端点中最靠右者

因为还要区间取反,所以对应的维护最大前缀和以及所有最大前缀和对应端点中最靠右者。

直接做是两个 的,把二分换成线段树上二分是 的。

[省选联考 2020 A/B 卷] 冰火战士

新科技:树状数组上倍增。

看完题后第一个想法是二分一个温度,然后数据结构维护一下求和。

首先,在温度固定的情况下,双方消耗的总能量之和为总能量小的那一方的二倍。

所以为了让和最大,我们希望双方的总能量更接近。

我们可以把双方抽象成两个总能量关于温度变化的函数,那么我们想找的就是函数的交点。

但这两个函数是离散的,所以我们二分找到最优点。

表示冰系的总能量, 表示火系的总能量。

我们二分找到最大的 ,使得 ,再二分找到最小的 ,使得

但是直接二分是两个 ,所以把二分换成树状数组上倍增。

[USACO16FEB] Load Balancing P

看到最大值最小想到二分答案。

但这个二分事实上是没有意义的,因为可行性也不好做:我们依旧要找到最优情况。所以直接考虑最优性怎么做。

我们枚举水平方向的线,用树状数组分别维护上下两部分的奶牛数。

然后我们想找出来最优的竖直方向的线。

为左侧两部分的最大值, 为右侧两部分的最大值。

因为我们想让每个部分尽量平均,所以我们二分找到最大的 ,使得 ,再二分找到最小的 ,使得

只有这两条线会取到最优。

但是直接二分是两个 ,所以把二分换成树状数组上倍增。

感觉和上一道题很像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include"bits/stdc++.h"
#define re register
#define int long long
#define lb(x) (x&(-x))
using namespace std;
const int maxn=1e5+10,inf=1e18;
int n,cntx,cnty;
int tmpx[maxn],tmpy[maxn];
int buc1[maxn],buc2[maxn];
struct point{
int x,y;
inline bool operator < (const point &a)const{
if(y==a.y) return x<a.x;
return y<a.y;
}
}t[maxn];
struct BIT{
int tr[maxn];
inline void add(int x,int val){while(x<=cntx) tr[x]+=val,x+=lb(x);}
inline int query(int x){int res=0;while(x) res+=tr[x],x-=lb(x);return res;}
}a,b;
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(re int i=1;i<=n;++i){
cin>>t[i].x>>t[i].y;
tmpx[i]=t[i].x,tmpy[i]=t[i].y;
}
sort(tmpx+1,tmpx+n+1);sort(tmpy+1,tmpy+n+1);
cntx=unique(tmpx+1,tmpx+n+1)-(tmpx+1);
cnty=unique(tmpy+1,tmpy+n+1)-(tmpy+1);
for(re int i=1;i<=n;++i){
t[i].x=lower_bound(tmpx+1,tmpx+cntx+1,t[i].x)-tmpx;
t[i].y=lower_bound(tmpy+1,tmpy+cnty+1,t[i].y)-tmpy;
}
sort(t+1,t+n+1);
for(re int i=1;i<=n;++i){
a.add(t[i].x,1);
++buc1[t[i].x];
}
int sum1=n,sum2=0,ans=inf,now=1;
for(re int i=1;i<=cnty;++i){
while(now<=n&&t[now].y<i){
a.add(t[now].x,-1),b.add(t[now].x,1);
--sum1,++sum2;
--buc1[t[now].x],++buc2[t[now].x];
++now;
}
int pos=0,s1=0,s2=0;
for(re int j=__lg(cntx);j>=0;--j){
if(pos+(1<<j)<=cntx&&max(s1+a.tr[pos+(1<<j)],s2+b.tr[pos+(1<<j)])<=max(sum1-(s1+a.tr[pos+(1<<j)]),sum2-(s2+b.tr[pos+(1<<j)]))){
s1+=a.tr[pos+(1<<j)];
s2+=b.tr[pos+(1<<j)];
pos+=(1<<j);
}
}
ans=min(ans,max({s1,s2,sum1-s1,sum2-s2}));
if(pos<cntx){
s1+=buc1[pos+1],s2+=buc2[pos+1];
ans=min(ans,max({s1,s2,sum1-s1,sum2-s2}));
}
}
cout<<ans;
return 0;
}

Greedy Shopping *2600

对于操作一,可能第一眼感觉要上吉司机。

但题目保证了序列单调不增,所以直接二分找到第一个小于 的位置即可。

把二分换成线段树上二分可以做到一个

对于操作二,第一想法是每次都二分找到第一个小于等于 的位置,然后从这个位置向后二分一个长度,看最多能取多少。

这是三个 的。

但如果把第一个二分换成线段树上二分,那么这是两个 的。

总复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include"bits/stdc++.h"
#define re register
#define int long long
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
using namespace std;
const int maxn=2e5+10;
int n,m,rt,segcnt;
int a[maxn];
struct tree{
int ls,rs;
int sum,mn,tag;
}tr[maxn<<1];
inline void up(int p){
tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum;
tr[p].mn=min(tr[ls(p)].mn,tr[rs(p)].mn);
}
inline void cov(int l,int r,int val,int p){
tr[p].mn=tr[p].tag=val;
tr[p].sum=(r-l+1)*val;
}
inline void down(int l,int r,int mid,int p){
if(tr[p].tag){
cov(l,mid,tr[p].tag,ls(p));
cov(mid+1,r,tr[p].tag,rs(p));
tr[p].tag=0;
}
}
inline void build(int l,int r,int &p){
p=++segcnt;
if(l==r){tr[p].sum=tr[p].mn=a[l];return;}
int mid=(l+r)>>1;
build(l,mid,ls(p)),build(mid+1,r,rs(p));
up(p);
}
inline void update(int l,int r,int L,int R,int val,int p){
if(l>=L&&r<=R){cov(l,r,val,p);return;}
int mid=(l+r)>>1;
down(l,r,mid,p);
if(L<=mid) update(l,mid,L,R,val,ls(p));
if(R>mid) update(mid+1,r,L,R,val,rs(p));
up(p);
}
inline int query1(int l,int r,int val,int p){
if(l==r){
if(tr[p].sum<val) return l;
return 1926081719491001071;
}
int mid=(l+r)>>1;
down(l,r,mid,p);
if(tr[ls(p)].mn<val) return query1(l,mid,val,ls(p));
else return query1(mid+1,r,val,rs(p));
}
inline int query2(int l,int r,int L,int R,int &w,int p){
if(l>R||r<L||w<tr[p].mn) return 0;
if(l>=L&&r<=R&&w>=tr[p].sum){
w-=tr[p].sum;
return r-l+1;
}
int mid=(l+r)>>1,res=0;
down(l,r,mid,p);
if(L<=mid) res+=query2(l,mid,L,R,w,ls(p));
if(R>mid) res+=query2(mid+1,r,L,R,w,rs(p));
return res;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(re int i=1;i<=n;++i) cin>>a[i];
build(1,n,rt);
int op,x,y;
while(m--){
cin>>op>>x>>y;
if(op==1){
int pos=query1(1,n,y,1);
if(pos<=x) update(1,n,pos,x,y,1);
}
else cout<<query2(1,n,x,n,y,1)<<'\n';
}
return 0;
}

Friends and Subsequences *2100

简单题。

对于一个固定的左端点 ,不难发现 都有单调性。

所以想到二分,然后用 ST 表查最值即可。

Hdu 4391 Paint The Wall

读完题后第一想法是对每个颜色开一颗线段树,然后分别维护。

对于操作一,我们用 set 维护所有颜色段。

每一次区间推平,我们都暴力的把区间里的所有颜色段修改掉,然后加进去一个新的颜色段。

听起来很暴力,但均摊复杂度是对的。

代码有一车细节,写吐了。

这就是珂朵莉吧?

好像也不太是。

复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include"bits/stdc++.h"
#define re register
#define ls(p) tr[p].ls
#define rs(p) tr[p].rs
using namespace std;
const int maxn=1e5+10,maxm=1e7+10;
int n,m,segcnt,cnt;
int a[maxn],tmp[maxn<<1];
int rt[maxn];
struct tree{
int ls,rs;
int sum,tag;
}tr[maxm];
struct Query{
int op,l,r,c;
}q[maxn];
struct line{
int l,r,c;
inline bool operator < (const line &a)const{
if(r==a.r) return l<a.l;
return r<a.r;
}
};
set<line> s;
inline void up(int p){tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum;}
inline void add(int l,int r,int val,int p){
tr[p].tag+=val;
tr[p].sum+=(r-l+1)*val;
}
inline void down(int l,int r,int mid,int p){
if(tr[p].tag){
if(!ls(p)) ls(p)=++segcnt;
if(!rs(p)) rs(p)=++segcnt;
add(l,mid,tr[p].tag,ls(p));
add(mid+1,r,tr[p].tag,rs(p));
tr[p].tag=0;
}
}
inline void update(int l,int r,int L,int R,int val,int &p){
if(!p) p=++segcnt;
if(l>=L&&r<=R){add(l,r,val,p);return;}
int mid=(l+r)>>1;
down(l,r,mid,p);
if(L<=mid) update(l,mid,L,R,val,ls(p));
if(R>mid) update(mid+1,r,L,R,val,rs(p));
up(p);
}
inline int query(int l,int r,int L,int R,int p){
if(!p) return 0;
if(l>=L&&r<=R) return tr[p].sum;
int mid=(l+r)>>1,res=0;
down(l,r,mid,p);
if(L<=mid) res+=query(l,mid,L,R,ls(p));
if(R>mid) res+=query(mid+1,r,L,R,rs(p));
return res;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(re int i=1;i<=n;++i) cin>>a[i],tmp[++cnt]=a[i];
for(re int i=1;i<=m;++i){
cin>>q[i].op>>q[i].l>>q[i].r>>q[i].c;
++q[i].l,++q[i].r;
tmp[++cnt]=q[i].c;
}
sort(tmp+1,tmp+cnt+1);
cnt=unique(tmp+1,tmp+cnt+1)-(tmp+1);
for(re int i=1;i<=n;++i) a[i]=lower_bound(tmp+1,tmp+cnt+1,a[i])-tmp;
for(re int i=1;i<=m;++i) q[i].c=lower_bound(tmp+1,tmp+cnt+1,q[i].c)-tmp;
for(re int i=1;i<=n;++i) update(1,n,i,i,1,rt[a[i]]),s.insert({i,i,a[i]});
for(re int i=1;i<=m;++i){
if(q[i].op==1){
set<line>::iterator it1=s.lower_bound({-1,q[i].l,0}),it2=s.lower_bound({-1,q[i].r,0});
if(it1==it2){
update(1,n,max(q[i].l,it1->l),min(q[i].r,it1->r),-1,rt[it1->c]);
update(1,n,max(q[i].l,it1->l),min(q[i].r,it1->r),1,rt[q[i].c]);
if(it1->l<q[i].l){
line p1={it1->l,q[i].l-1,it1->c};
s.insert(p1);
}
if(it1->r>q[i].r){
line p2={q[i].r+1,it1->r,it1->c};
s.insert(p2);
}
s.erase(it1),s.insert({q[i].l,q[i].r,q[i].c});
continue;
}
++it1;
for(auto it=it1;it!=it2;++it){
update(1,n,it->l,it->r,-1,rt[it->c]);
update(1,n,it->l,it->r,1,rt[q[i].c]);
}
it1=s.erase(it1,it2);
--it1;
if(it1->l<q[i].l){
line p1={it1->l,q[i].l-1,it1->c};
update(1,n,q[i].l,it1->r,-1,rt[it1->c]);
update(1,n,q[i].l,it1->r,1,rt[q[i].c]);
s.erase(it1),s.insert(p1);
}
else{
update(1,n,q[i].l,it1->r,-1,rt[it1->c]);
update(1,n,q[i].l,it1->r,1,rt[q[i].c]);
s.erase(it1);
}
if(it2->r>q[i].r){
line p2={q[i].r+1,it2->r,it2->c};
update(1,n,it2->l,q[i].r,-1,rt[it2->c]);
update(1,n,it2->l,q[i].r,1,rt[q[i].c]);
s.erase(it2),s.insert(p2);
}
else{
update(1,n,it2->l,q[i].r,-1,rt[it2->c]);
update(1,n,it2->l,q[i].r,1,rt[q[i].c]);
s.erase(it2);
}
s.insert({q[i].l,q[i].r,q[i].c});
}
else cout<<query(1,n,q[i].l,q[i].r,rt[q[i].c])<<'\n';
}
return 0;
}

Hdu 5213 Lucky

简单莫队。

两个区间分别选数不好做,差分一下。

如果只从一个区间选数,那么很好做。

然后我们把两个区间分别拆成 即可。

没看到多测和访问负下标为我贡献了两发罚时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include"bits/stdc++.h"
#define re register
#define int long long
using namespace std;
const int maxn=3e4+10;
int n,k,m,cnt,siz,now;
int a[maxn],buc[maxn],ans[maxn];
struct query{
int l,r,op,id;
}q[maxn<<2];
inline bool cmp(query a,query b){return ((a.l/siz)^(b.l/siz))?(a.l/siz)<(b.l/siz):((a.l/siz)&1)?a.r<b.r:a.r>b.r;}
inline void add(int x){
++buc[x];
if(x<=k) now+=buc[k-x];
}
inline void del(int x){
--buc[x];
if(x<=k) now-=buc[k-x];
}
inline void clear(){
cnt=now=0;
memset(buc,0,sizeof buc);
memset(ans,0,sizeof ans);
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
while(cin>>n){
clear();
cin>>k;
for(re int i=1;i<=n;++i) cin>>a[i];
cin>>m;
for(re int i=1,l1,r1,l2,r2;i<=m;++i){
cin>>l1>>r1>>l2>>r2;
q[++cnt]={l1,r2,1,i};
q[++cnt]={l1,l2-1,-1,i};
q[++cnt]={r1+1,r2,-1,i};
q[++cnt]={r1+1,l2-1,1,i};
}
siz=sqrt(n);
sort(q+1,q+cnt+1,cmp);
int l=1,r=0;
for(re int i=1;i<=cnt;++i){
while(r<q[i].r) add(a[++r]);
while(l>q[i].l) add(a[--l]);
while(r>q[i].r) del(a[r--]);
while(l<q[i].l) del(a[l++]);
ans[q[i].id]+=now*q[i].op;
}
for(re int i=1;i<=m;++i) cout<<ans[i]<<'\n';
}
return 0;
}