建议先弄懂
T
2
T2
T
2
,考虑使用单调栈+线段树/树状数组在固定右端点的情况下维护任意一个左端点到右端点的区间最值。
代码风格不是平常的风格,平常代码一行只写一句,非常长所以放上来的时候
T0:Edu124 E. Sum of Matchings
上课前讲的一道近期cf题目 待补。
然后上课刚开始,dls大致介绍了一下树状数组上二分,待补。
T1:abc234_g Divide a Sequence
这道题的大致题意是,给定一个长为
n
n
n
的数组,我们可以对他进行一个
划分
,每次划分的
v
a
l
u
e
s
values
v
a
l
u
e
s
是对所有部分的(最大值-最小值)进行累积。求所有划分的
v
a
l
u
e
s
values
v
a
l
u
e
s
的累和。
划分:在不删除、移动数组种任何元素的情况下,将数组分割成若干子数组如{[1,2,3]}可以划分成{[1],[2,3]}或{[1],[2],[3]}等
大致思路:
容易
得到
f
(
i
)
=
∑
j
=
0
i
−
2
f
(
j
)
∗
m
a
x
(
a
[
j
+
1
]
,
.
.
,
a
[
i
]
)
−
∑
j
=
0
i
−
2
f
(
j
)
∗
m
i
n
(
a
[
j
+
1
]
,
.
.
.
,
a
[
i
]
)
f(i)=\sum_{j=0}^{i-2}f(j)*max(a[j+1],..,a[i])-\sum_{j=0}^{i-2}f(j)*min(a[j+1],…,a[i])
f
(
i
)
=
∑
j
=
0
i
−
2
f
(
j
)
∗
m
a
x
(
a
[
j
+
1
]
,
.
.
,
a
[
i
]
)
−
∑
j
=
0
i
−
2
f
(
j
)
∗
m
i
n
(
a
[
j
+
1
]
,
.
.
.
,
a
[
i
]
)
然后考虑维护(
m
i
n
min
m
i
n
维护和
m
a
x
max
m
a
x
维护相同)
∑
j
=
0
i
−
2
f
(
j
)
∗
m
a
x
(
a
[
j
+
1
]
,
.
.
,
a
[
i
]
)
\sum_{j=0}^{i-2}f(j)*max(a[j+1],..,a[i])
∑
j
=
0
i
−
2
f
(
j
)
∗
m
a
x
(
a
[
j
+
1
]
,
.
.
,
a
[
i
]
)
这里的
m
a
x
max
m
a
x
可以通过单调栈维护
AC code:
#include <bits/stdc++.h>
#define mod 998244353
#define int long long
#define lowbit(x) (x&(-x))
using namespace std;
int n,k,m,cnt;
int a[300005];
int dp[300005];
stack <int>s1,s2;
int tr[300005];
void add(int x,int k){for(int i=x;i<=n;i+=lowbit(i)) (tr[i]+=k)%=mod;}
int qr(int x,int res=0){
for(int i=x;i>0;i-=lowbit(i)) (res+=tr[i])%=mod;
if(x>=0)(res+=1)%=mod;
return res;
}
int ask(int l,int r){return (mod+qr(r)-qr(l-1))%mod;}
void solve(){
cin>>n;dp[0]=1;
for(int i=1;i<=n;i++){cin>>a[i];}
s1.push(0);s2.push(0);
int maxsum=0,minsum=0;
for(int i=1;i<=n;i++){
while(s1.top()!=0&&a[s1.top()]<=a[i]){
int u=s1.top();s1.pop();
int dx=ask(s1.top(),u-1);
maxsum=(maxsum-dx*a[u]%mod+mod)%mod;
}
maxsum=(maxsum+ask(s1.top(),i)*a[i]%mod)%mod;
s1.push(i);
while(s2.top()!=0&&a[s2.top()]>=a[i]){
int u=s2.top();s2.pop();
int dx=ask(s2.top(),u-1);
minsum=(minsum-dx*a[u]%mod+mod)%mod;
}
minsum=(minsum+ask(s2.top(),i)*a[i]%mod+mod)%mod;
s2.push(i);
dp[i]=(maxsum-minsum+mod)%mod;
add(i,dp[i]);
}
cout<<dp[n]<<endl;
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
int t=1;
while (t--)
solve();
}
T2:CF1635F Closest Pair
题目大意:给定
n
n
n
个点,每个点有一个
x
i
,
w
i
x_i,w_i
x
i
,
w
i
(每个点按照
x
i
x_i
x
i
从小到大给出)。对于任意两点,他们的
v
a
l
=
∣
x
i
−
x
j
∣
∗
(
w
i
+
w
j
)
val=\vert x_i-x_j \vert *(w_i+w_j)
v
a
l
=
∣
x
i
−
x
j
∣
∗
(
w
i
+
w
j
)
。
给定
n
n
n
个询问,求每个询问给定的
[
l
i
,
r
i
]
[l_i,r_i]
[
l
i
,
r
i
]
中,
v
a
l
val
v
a
l
最小的点对
大致思路 :
1.使用单调栈得出所有可以成为答案的点对,且需要证明能够成为答案的点对至多
2n
2n
2
n
对
2.考虑将所有提问离线按照右端点排序,并用树状数组/线段树维护区间最小值
①证明过程:在所有点按照
x
x
x
坐标排序的情况下,对于两个不同点
(
x
1
,
w
1
)
,
(
x
2
,
w
2
)
(x_1,w_1),(x_2,w_2)
(
x
1
,
w
1
)
,
(
x
2
,
w
2
)
,若
x
1
<
x
2
&
&
w
1
≥
w
2
x_1<x_2 \&\& w_1\geq w_2
x
1
<
x
2
&
&
w
1
≥
w
2
,那么显然
(
x
1
,
w
1
)
(x_1,w_1)
(
x
1
,
w
1
)
与
(
x
2
,
w
2
)
(x_2,w_2)
(
x
2
,
w
2
)
右侧任意一点成为答案的时候,选择
(
x
2
,
w
2
)
(x_2,w_2)
(
x
2
,
w
2
)
一定更优。上述的所有点显然是可以使用一个栈内递增的单调栈维护。在每次对栈操作时(无论是弹出还是插入前),栈顶段与当前端点一定是一个可能成为答案的点对。单调栈操作至多操作
2
∗
n
−
2
2*n-2
2
∗
n
−
2
次,所以能够成为答案的点对至多
2
∗
n
−
2
2*n-2
2
∗
n
−
2
对。
AC code
#include <bits/stdc++.h>
#define mod 998244353
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
#define mid ((l+r)>>1)
#define ls rt<<1
#define rs rt<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
using namespace std;
int n,l,r,k,m,cnt,q;
stack <int>s;
int tr[1200005];
int fk[300005];
struct node {
int l,r,id;
bool operator <(const node &t)const{return r<t.r||r==t.r&&l<t.l;}
}ans[600005],a[300005],ask[300005];
void push_up(int rt){tr[rt]=min(tr[rt<<1],tr[rt<<1|1]);}
void build(int rt,int l,int r){
if(l==r){tr[rt]=4000000000000000001;return;}
build(lson);build(rson);
push_up(rt);
}
void update(int rt,int l,int r,int ql,int qr,int k){
if(l>=ql&&r<=qr){tr[rt]=min(tr[rt],k);return;}
if(mid>=ql)update(lson,ql,qr,k);
if(mid<qr)update(rson,ql,qr,k);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr,int res=4000000000000000001){
if(l>=ql&&r<=qr) return tr[rt];
if(mid>=ql)res=min(res,query(lson,ql,qr));
if(mid<qr)res=min(res,query(rson,ql,qr));
return res;
}
int get(node t){int u=t.l,v=t.r;return abs(a[v].l-a[u].l)*(a[v].r+a[u].r);}
void solve(){
cin>>n>>q;
build(1,1,n);
for(int i=1;i<=n;i++){
cin>>a[i].l>>a[i].r;
while(!s.empty()&&a[s.top()].r>=a[i].r){
ans[++cnt]={s.top(),i,0};
s.pop();
}
if(!s.empty()) ans[++cnt]={s.top(),i,0};
s.push(i);
}
for(int i=1;i<=q;i++){cin>>ask[i].l>>ask[i].r;ask[i].id=i;}
sort(ask+1,ask+1+q);
for(int i=1,j=1;j<=q&&i<=cnt+1;i++){
while(j<=q&&(ask[j].r<ans[i].r||i==cnt+1)){
fk[ask[j].id]=query(1,1,n,ask[j].l,n);
j++;
}
if(i==cnt+1)break;
update(1,1,n,ans[i].l,ans[i].l,get(ans[i]));
}
for(int i=1;i<=q;i++){cout<<fk[i]<<endl;}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
int t=1;
while (t--)
solve();
}
T3:洛谷P1972 HH的项链
题目大意:不重要,自己点进去看
大致思路:
T
2
T2
T
2
中思路
2
2
2
的模板,离线询问后用树状数组/线段树维护区间性质。
AC code
#include <bits/stdc++.h>
#define mod 998244353
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
using namespace std;
int n,l,r,k,m,cnt;
int a[1000005],last[1000005];
stack <int>s1,s2;
int tr[1000005];
int fk[1000005];
void add(int x,int k){for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=k;}
int qr(int x,int res=0){for(int i=x;i>0;i-=lowbit(i)) res+=tr[i];return res;}
int ask(int l,int r){return (mod+qr(r)-qr(l-1))%mod;}
struct node {
int l,r,id;
bool operator <(const node &t)const{return r<t.r||r==t.r&&l<t.l;}
}ans[1000005];
void solve(){
cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}
cin>>m;for(int i=1;i<=m;i++){cin>>ans[i].l>>ans[i].r;ans[i].id=i;}
sort(ans+1,ans+1+m);
for(int i=1,j=1;j<=m&&i<=n;i++){
add(i,1);
if(last[a[i]]!=0){add(last[a[i]],-1);}
last[a[i]]=i;
while(j<=m&&ans[j].r==i){
fk[ans[j].id]=ask(ans[j].l,ans[j].r);
j++;
}
}
for(int i=1;i<=m;i++){cout<<fk[i]<<endl;}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
int t=1;
while (t--)
solve();
}
T4:CF526F Pudding Monsters
大致题意:给定一个
n
×
n
n \times n
n
×
n
的棋盘,其中有
n
n
n
个棋子,每行每列恰好有一个棋子。
求有多少个
k
×
k
k \times k
k
×
k
的子棋盘中恰好有
k
k
k
个棋子。
题解:首先因为题目保证每行每列都有一个棋子,所以我们可以将点对
降维
存储为
a
[
x
]
=
y
a[x]=y
a
[
x
]
=
y
。
容易想到
对于任意一个
[
l
,
r
]
[l,r]
[
l
,
r
]
区间内,假设区间最大值为
m
a
x
n
maxn
m
a
x
n
,区间最小值是
m
i
n
n
minn
m
i
n
n
,那么对于这个区间,如果刚好每行每列有一个棋子,则满足
m
a
x
n
−
m
i
n
n
=
r
−
l
maxn-minn=r-l
m
a
x
n
−
m
i
n
n
=
r
−
l
。将等式右边移到左边,变成
(
m
a
x
n
−
m
i
n
n
)
−
(
r
−
l
)
=
0
(maxn-minn)-(r-l)=0
(
m
a
x
n
−
m
i
n
n
)
−
(
r
−
l
)
=
0
。题目转变为,求满足以上等式的区间数量。
考虑固定右端点
,当右端点固定为
r
r
r
的时候,我们只需要判断左端点选择
1
1
1
到
r
r
r
时对应区间的性质,我们需要一个数据结构,保证维护每次的点
l
l
l
到当前固定右端点的区间的性质。
重看上述等式
(
m
a
x
n
−
m
i
n
n
)
−
(
r
−
l
)
=
0
(maxn-minn)-(r-l)=0
(
m
a
x
n
−
m
i
n
n
)
−
(
r
−
l
)
=
0
,容易证明
m
a
x
n
−
m
i
n
n
≥
r
−
l
maxn-minn \geq r-l
m
a
x
n
−
m
i
n
n
≥
r
−
l
,所以我们当等式等于
0
0
0
时,即是区间最小值,所以我们只需要维护
区间最小值且等于0的个数
。
考虑维护上述等式
4
4
4
个变量
1.
r
r
r
,当区间右端点
r
r
r
变成
r
−
1
r-1
r
−
1
时,所有的左区间对应的区间长度+1,我们只需要对线段树全部
−
1
-1
−
1
即可。
2.
l
l
l
,显然,所有的
l
l
l
都是在每个节点上都是一个常量,是不会变化的,我们在建立线段树的时候直接确定即可。
3.
m
a
x
n
maxn
m
a
x
n
,表示区间
l
l
l
到
r
r
r
的区间最大值,可以通过单调栈+线段树维护。
4.
m
i
n
n
minn
m
i
n
n
,同3。
将上述操作实现之后在固定每个右端点,每次查
[
1
,
r
]
[1,r]
[
1
,
r
]
的区间最小值且等于
0
0
0
的个数即可。
复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O
(
n
l
o
g
n
)
AC code
#include <bits/stdc++.h>
#define mod 998244353
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
#define mid ((l+r)>>1)
#define ls rt<<1
#define rs rt<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
using namespace std;
const int N = 100005;
string yes="YES\n",no="NO\n";
int n,op,l,r,k,m,q,ans;
stack <int>s1,s2;
int a[300005];
int cnt[1200005],minx[1200005],tr[1200005],lazy[1200005];
void push_up(int rt){
minx[rt]=min(minx[ls],minx[rs]);cnt[rt]=0;
if(minx[rt]==minx[ls])cnt[rt]+=cnt[ls];
if(minx[rt]==minx[rs])cnt[rt]+=cnt[rs];
}
void build(int rt,int l,int r){
if(l==r){minx[rt]=l;cnt[rt]=1;return;}
build(lson);build(rson);
cnt[rt]=cnt[ls];minx[rt]=minx[ls];
}
void push_down(int rt){
minx[ls]+=lazy[rt];minx[rs]+=lazy[rt];
lazy[ls]+=lazy[rt];lazy[rs]+=lazy[rt];
lazy[rt]=0;
}
void update(int rt,int l,int r,int ql,int qr,int k){
if(l>=ql&&r<=qr){minx[rt]+=k;lazy[rt]+=k;return;}
push_down(rt);
if(mid>=ql)update(lson,ql,qr,k);
if(mid<qr)update(rson,ql,qr,k);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr,int res=0){
if(l>=ql&&r<=qr) return cnt[rt];
push_down(rt);
if(mid>=ql)res+=query(lson,ql,qr);
if(mid<qr)res+=query(rson,ql,qr);
return res;
}
void solve(){
cin>>n;s1.push(0);s2.push(0);
for(int i=1;i<=n;i++){int x,y;cin>>x>>y;a[x]=y;}
build(1,1,n);
for(int i=1;i<=n;i++){
update(1,1,n,1,n,-1);
while(s1.top()!=0&&a[s1.top()]<a[i]){
int u=s1.top();s1.pop();
update(1,1,n,s1.top()+1,u,a[i]-a[u]);
}
while(s2.top()!=0&&a[s2.top()]>a[i]){
int u=s2.top();s2.pop();
update(1,1,n,s2.top()+1,u,a[u]-a[i]);
}
s1.push(i);s2.push(i);
ans+=query(1,1,n,1,i);
}
cout<<ans;
}
signed main(){
int t=1;
while (t--)
solve();
}
T5:CF765F Souvenirs
题目大意:给定一个数组,
q
q
q
次询问,每次问
【
l
i
,
r
i
】
【l_i,r_i】
【
l
i
,
r
i
】
之间相差最近两个数的差值。
简要题解:仍然是首先离线询问,考虑维护每个
l
l
l
到
r
r
r
区间的对应的答案存储到
l
l
l
上。
权值线段树维护区间对应最大值(每次单点插入
a
[
i
]
a[i]
a
[
i
]
处插入
i
i
i
)
线段树维护
l
l
l
到对应区间的最小值。
考虑对于大于
a
[
i
]
a[i]
a
[
i
]
部分维护答案:
首先在权值线段树中找到
a
[
i
]
a[i]
a
[
i
]
到
1
e
9
1e9
1
e
9
中最大的值,在线段树中对应位置更新为
a
[
u
]
−
a
[
i
]
a[u]-a[i]
a
[
u
]
−
a
[
i
]
,然后继续找到
a
[
i
]
a[i]
a
[
i
]
到
(
a
[
i
]
+
a
[
u
]
)
/
2
(a[i]+a[u])/2
(
a
[
i
]
+
a
[
u
]
)
/
2
区间内最大值,进行同样操作,知道找不到值或
a
[
u
]
=
=
a
[
i
]
a[u]==a[i]
a
[
u
]
=
=
a
[
i
]
为止。
对于小于
a
[
i
]
a[i]
a
[
i
]
部分维护同上。
复杂度
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O
(
n
l
o
g
2
n
)
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
#define mid ((l+r)>>1)
using namespace std;
const int N = 100005;
int n,l,r,k,m,q,x;
int a[100005],ans[300005];
int tr[4000005],ls[4000005],rs[4000005],root,cnt;
int c[400005];//命名后面带1的是线段树维护区间最小值
void push_up1(int rt){c[rt]=min(c[rt<<1],c[rt<<1|1]);}
void update1(int rt,int l,int r,int k,int x){
if(l==r) {c[rt]=min(c[rt],x);return;}
if(mid>=k)update1(rt<<1,l,mid,k,x);
if(mid<k)update1(rt<<1|1,mid+1,r,k,x);
push_up1(rt);
}
int query1(int rt,int l,int r,int ql,int qr,int res=1000000000){
if(l>=ql&&r<=qr)return c[rt];
if(mid>=ql)res=min(res,query1(rt<<1,l,mid,ql,qr));
if(mid<qr)res=min(res,query1(rt<<1|1,mid+1,r,ql,qr));
return res;
}
struct node{
int l,r,id;
bool operator < (const node &t) const{return r<t.r;}
}ri[300005];
void push_up(int rt){tr[rt]=max(tr[ls[rt]],tr[rs[rt]]);}//动态开点维护区间最大值
void update(int &rt,int l,int r,int k,int x){
if(!rt) {rt=++cnt;}
if(l==r){tr[rt]=max(tr[rt],x);return;}
if(mid>=k)update(ls[rt],l,mid,k,x);
if(mid<k)update(rs[rt],mid+1,r,k,x);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr,int res=0){
if(!rt||l>=ql&&r<=qr) return tr[rt];
if(mid>=ql)res=max(res,query(ls[rt],l,mid,ql,qr));
if(mid<qr)res=max(res,query(rs[rt],mid+1,r,ql,qr));
return res;
}
void solve(){
memset(c,0x3f,sizeof(c));
cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}
cin>>m;for(int i=1;i<=m;i++){cin>>ri[i].l>>ri[i].r;ri[i].id=i;}
sort(ri+1,ri+1+m);
update(root,0,1000000000,a[1],1);
for(int i=2,j=1;i<=n&&j<=m;i++){
int u=query(root,0,1000000000,a[i],1000000000),v=query(root,0,1000000000,0,a[i]);
while(u){
update1(1,1,n,u,a[u]-a[i]);
if(a[u]==a[i])break;
u=query(root,0,1000000000,a[i],(a[i]+a[u])/2);//往靠近a[i]方向取整所以向下取整
}
while(v){
update1(1,1,n,v,a[i]-a[v]);
if(a[v]==a[i])break;
v=query(root,0,1000000000,(a[v]+a[i]+1)/2,a[i]);//往靠近a[i]方向取整所以向上取整
}
update(root,0,1000000000,a[i],i);
while(j<=m&&ri[j].r==i){
int cao=query1(1,1,n,ri[j].l,i);
ans[ri[j].id]=cao;
j++;
}
}
for(int i=1;i<=m;i++){cout<<ans[i]<<endl;}
}
signed main(){
int t=1;
while (t--)
solve();
}
T6:CF438D The Child and Sequence
题目大意:给一个数组,支持区间取模,单点修改,查区间和。
题目思路:首先对三个操作写一个暴力,区间求和与单点修改操作都是基本线段树操作,直接套板子即可。所以考虑区间取模。
考虑一个简单的剪枝,假设输入的模数为
k
k
k
,对于区间内的所有点,只有大于
k
k
k
的数会遭受修改,而所有比k小的数都是不变的,所以在线段树上,我们统计一个区间
m
a
x
max
m
a
x
,如果区间
m
a
x
<
k
max<k
m
a
x
<
k
,那么我们这一段区间内的所有数其实都不会发生改变,不需要
u
p
d
a
t
e
update
u
p
d
a
t
e
。
这是一个非常暴力的写法,但是我们只需要增加一个这个判断即可通过这题。
粗略证明:考虑到取模操作 ,容易证明在
a
≤
k
a\leq k
a
≤
k
的情况下
a
%
k
<
a
2
a \% k < \frac a2
a
%
k
<
2
a
(可以结合
g
c
d
gcd
g
c
d
的复杂度证明莱思考),所以对任意一点,取模操作至多
l
o
g
log
l
o
g
次,单点修改至多增加
l
o
g
log
l
o
g
次操作,所以整体操作次数不会超过
O
(
n
l
o
o
g
n
)
O(nloogn)
O
(
n
l
o
o
g
n
)
(还是
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O
(
n
l
o
g
2
n
)
?不会进行复杂度分析,但是肯定是可以通过的复杂度)。
AC code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define lowbit(x) (x&(-x))
#define mid ((l+r)>>1)
#define ls rt<<1
#define rs rt<<1|1
#define lson ls,l,mid
#define rson rs,mid+1,r
using namespace std;
int n,op,l,r,k,m,q,ans,x;
int a[100005];
int tr[400005],maxn[400005];
void push_up(int rt){tr[rt]=tr[ls]+tr[rs];maxn[rt]=max(maxn[ls],maxn[rs]);}
void build(int rt,int l,int r){
if(l==r){cin>>tr[rt];maxn[rt]=tr[rt];return;}
build(lson);build(rson);
push_up(rt);
}
void update(int rt,int l,int r,int ql,int qr,int k){//单点修改为k,图省事直接用的区间修改写的单点修改
if(l>=ql&&r<=qr){tr[rt]=maxn[rt]=k;return;}
if(mid>=ql)update(lson,ql,qr,k);
if(mid<qr)update(rson,ql,qr,k);
push_up(rt);
}
void update1(int rt,int l,int r,int ql,int qr,int k){//区间模k,区间最大值小于k直接返回,否则暴力修改
if(l==r){tr[rt]%=k;maxn[rt]=tr[rt];return;}
if(l>=ql&&r<=qr&&maxn[rt]<k){return;}
if(mid>=ql&&maxn[ls]>=k)update1(lson,ql,qr,k);
if(mid<qr&&maxn[rs]>=k)update1(rson,ql,qr,k);
push_up(rt);
}
int query(int rt,int l,int r,int ql,int qr,int res=0){//查询区间和
if(l>=ql&&r<=qr) return tr[rt];
if(mid>=ql)res+=query(lson,ql,qr);
if(mid<qr)res+=query(rson,ql,qr);
return res;
}
void solve(){
cin>>n>>q;build(1,1,n);
while(q--){
cin>>op;
if(op==1){cin>>l>>r;cout<<query(1,1,n,l,r)<<endl;}
else if(op==2){cin>>l>>r>>x;update1(1,1,n,l,r,x);}
else{cin>>k>>x;update(1,1,n,k,k,x);}
}
}
signed main(){
int t=1;
while (t--)
solve();
}
如果以上有错误之处可以评论指出。