25-暑期-来追梦noip-卷7 总结
好像目前为止只有我补了 D
开题顺序:A-C-D(B 没看)
分配时间:A 1h C 45min D 15min
A
预估 100,实际 60。
将序列转化为 \(1,-1\),其中大于等于 \(x\) 为 \(1\),小于的为 \(-1\)。
然后做一遍前缀和,容易发现区间和 \(\ge 0\) 的即为合法区间。
然后用树状数组统计一下即可,注意前缀和可能为负,所以需要离散化 / 偏移。
时间复杂度单 \(\log\)。
赛时写的离散化挂了 40 分 /ll
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,V=2e5+5,B=1e5;
int n,x;
int a[N],sum[N],tmp[N],tree[V];
int lowbit(int x){
return x&(-x);
}
void upd(int x,int y){
for(;x<=V-5;x+=lowbit(x))
tree[x]+=y;
}
int qry(int x){
int res=0;
for(;x;x-=lowbit(x))
res+=tree[x];
return res;
}
signed main(){
//freopen("T1.in","r",stdin);
//freopen("T1.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>x;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]=(a[i]>=x?1:-1);
sum[i]=sum[i-1]+a[i];
}
int ans=0;
for(int i=1;i<=n;i++){
ans+=qry(sum[i]+B);
if(sum[i]>=0)
ans++;
upd(sum[i]+B,1);
}
cout<<ans;
return 0;
}
B
预估 0,实际 0。
这种位运算题目,套路的,我们按位考虑贡献。
对于每一位,我们发现只有 \(1\) 才能产生贡献。
令第 \(i\) 位 \(1\) 的个数为 \(cnt_i\),则有 \(2^{cnt_i}-1\) 中选择的方案,每一种的贡献显然是 \(2^i\)。
直接这样统计是 \(\mathcal{O}(m \times 30n)\)(最多 \(30\) 位),无法接受,所以用树状数组优化一下统计的部分即可。具体而言,就是开 \(30\) 棵树状数组统计 \(n\) 个元素。时间复杂度可降至 \(\mathcal{O}(m \times 30 \log n)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,M=31;
const int MOD=1e9+7;
int n,m;
int a[N],p[N];
struct BIT{
int C[N];
int lowbit(int x){
return x&(-x);
}
void upd(int x,int y){
for(;x<=n;x+=lowbit(x))
C[x]+=y;
}
int qry(int x){
int res=0;
for(;x;x-=lowbit(x))
res+=C[x];
return res;
}
}tree[M];
signed main(){
//freopen("T2.in","r",stdin);
//freopen("T2.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
p[0]=1;
for(int i=1;i<=1000000;i++)
p[i]=(p[i-1]*2)%MOD;
for(int i=1;i<=n;i++)
for(int j=0;j<30;j++)
if((a[i]>>j)&1)
tree[j].upd(i,1);
cin>>m;
while(m--){
int op,x,y;
cin>>op>>x>>y;
if(op==1){
for(int i=0;i<30;i++)
if((a[x]>>i)&1)
tree[i].upd(x,-1);
a[x]=y;
for(int i=0;i<30;i++)
if((a[x]>>i)&1)
tree[i].upd(x,1);
}
else{
int ans=0;
for(int i=0;i<30;i++)
ans=(ans+((p[tree[i].qry(y)-tree[i].qry(x-1)]-1)%MOD)*(p[i]%MOD))%MOD;
cout<<ans<<'\n';
}
}
return 0;
}
总结:
- 位运算题目按位考虑贡献。
C
预估 0,实际 0。
第一个操作是区间所有元素与 \(k\) 取 \(\max\) 并赋值,可以使用线段树轻松维护。
第二个的话,需要我们找最小值、次小值......总共 \(x\) 个。从线段树的角度出发,假设区间 \([l,r]\) 找到了最小值,则它的次小值一定在以最小值所在位置分割,左右两半区间中的任一一个里边。因此,我们进行分治,直接用小根堆维护最小、次小等等这些值即可。时间复杂度双 \(\log\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
const int INF=1e18;
int n,m;
int a[N],tree[N<<2],pos[N<<2],tag[N<<2];
vector<int> vec;
struct NODE{
int l,r,w,p;
bool operator < (const NODE &b) const{
return w>b.w;
}
};
priority_queue<NODE> pq;
void pushup(int p){
tree[p]=INF;
if(tree[p]>tree[p<<1])
tree[p]=tree[p<<1],pos[p]=pos[p<<1];
if(tree[p]>tree[p<<1|1])
tree[p]=tree[p<<1|1],pos[p]=pos[p<<1|1];
}
void addtag(int p,int val){
tree[p]=max(tree[p],val);
tag[p]=max(tag[p],val);
}
void pushdown(int p){
if(!tag[p])
return;
addtag(p<<1,tag[p]);
addtag(p<<1|1,tag[p]);
tag[p]=0;
}
void build(int p,int lt,int rt){
if(lt==rt){
tree[p]=a[lt];
pos[p]=lt;
return;
}
int mid=(lt+rt)>>1;
build(p<<1,lt,mid);
build(p<<1|1,mid+1,rt);
pushup(p);
}
void upd(int p,int lt,int rt,int ql,int qr,int k){
if(lt>qr||rt<ql)
return;
if(ql<=lt&&rt<=qr){
addtag(p,k);
return;
}
pushdown(p);
int mid=(lt+rt)>>1;
upd(p<<1,lt,mid,ql,qr,k);
upd(p<<1|1,mid+1,rt,ql,qr,k);
pushup(p);
}
pair<int,int> qry(int p,int lt,int rt,int ql,int qr){
if(lt>qr||rt<ql)
return {INF,0};
if(ql<=lt&&rt<=qr)
return {tree[p],pos[p]};
pushdown(p);
int mid=(lt+rt)>>1;
pair<int,int> ret={INF,0};
if(ql<=mid){
auto cur=qry(p<<1,lt,mid,ql,qr);
if(ret.first>cur.first)
ret=cur;
}
if(qr>mid){
auto cur=qry(p<<1|1,mid+1,rt,ql,qr);
if(ret.first>cur.first)
ret=cur;
}
return ret;
}
signed main(){
//freopen("T3.in","r",stdin);
//freopen("T3.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
cin>>m;
while(m--){
int op,a,b,k,x;
cin>>op>>a>>b>>k;
if(op==1)
upd(1,1,n,a,b,k);
else{
cin>>x;
vec.clear();
while(!pq.empty())
pq.pop();
auto st=qry(1,1,n,a,b);
pq.push({a,b,st.first,st.second});
while(!pq.empty()){
auto cur=pq.top();
pq.pop();
if(cur.w>=k)
continue;
vec.push_back(cur.w);
auto u=qry(1,1,n,cur.l,cur.p-1);
auto v=qry(1,1,n,cur.p+1,cur.r);
pq.push({cur.l,cur.p-1,u.first,u.second});
pq.push({cur.p+1,cur.r,v.first,v.second});
if(vec.size()==x)
break;
}
if(vec.size()!=x)
cout<<"-1\n";
else{
sort(vec.begin(),vec.end());
for(int i:vec)
cout<<i<<' ';
cout<<'\n';
}
}
}
return 0;
}
总结:
- 找前 \(x\) 小:分治+小根堆。
D
首先,这个题里边有个长得很丑的式子,我们把它化简一下。
然后我们惊喜的发现,这玩意不就是前后缀最大值取 \(\min\) 吗。
首先前后缀最大值显然可以线段树维护。然后观察前后缀最大值的图像(这里就直接搬了)。

容易发现,前后缀最大值总是单调不减的,并且总会在最后达到一个峰值。
那么,对于第一个操作,峰值处求一个两边区间和就是答案。
对于第二个操作,我们发现这个 \(f_i\) 就是图像中较低的那条线,它显然具有单调性,所以我们可以二分出一个 \(\ge v\) 的左边界和右边界,然后和 \([l,r]\) 比较一下得到真实区间即可统计。
对于第三个操作,修改某一个位置总是只会影响后面的前缀,前面的后缀,所以直接区间修改即可,别忘了更新峰值。
时间复杂度当然是单 \(\log\)。
实现
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;
const int N=3e5+5;
int n,m;
int a[N],pre[N];
struct Sgt_Pepper_s_Lonely_Hearts_Club_Band{
int sum[N<<2],mx[N<<2],tag[N<<2];
void pushup(int p){
sum[p]=sum[p<<1]+sum[p<<1|1];
mx[p]=mx[p<<1|1];
}
void addtag(int p,int lt,int rt,int val){
mx[p]=tag[p]=val;
sum[p]=val*(rt-lt+1);
}
void pushdown(int p,int lt,int rt){
if(!tag[p])
return;
int mid=(lt+rt)>>1;
addtag(p<<1,lt,mid,tag[p]);
addtag(p<<1|1,mid+1,rt,tag[p]);
tag[p]=0;
}
void build(int p,int lt,int rt){
if(lt==rt){
sum[p]=mx[p]=pre[lt];
return;
}
int mid=(lt+rt)>>1;
build(p<<1,lt,mid);
build(p<<1|1,mid+1,rt);
pushup(p);
}
void upd(int p,int lt,int rt,int x,int val){
if(lt>=x&&mx[p]<=val){
addtag(p,lt,rt,val);
return;
}
if(lt==rt)
return;
pushdown(p,lt,rt);
int mid=(lt+rt)>>1;
if(mx[p<<1]<=val)
upd(p<<1|1,mid+1,rt,x,val);
if(mid>=x)
upd(p<<1,lt,mid,x,val);
pushup(p);
}
int getsum(int p,int lt,int rt,int x){
if(rt<=x)
return sum[p];
pushdown(p,lt,rt);
int mid=(lt+rt)>>1,res=getsum(p<<1,lt,mid,x);
if(mid<x)
res+=getsum(p<<1|1,mid+1,rt,x);
return res;
}
int getfir(int p,int lt,int rt,int val){
if(lt==rt)
return lt;
pushdown(p,lt,rt);
int mid=(lt+rt)>>1;
if(mx[p<<1]>=val)
return getfir(p<<1,lt,mid,val);
return getfir(p<<1|1,mid+1,rt,val);
}
}f,g;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
int maxi=-1e9,pos;
for(int i=1;i<=n;i++){
cin>>a[i];
if(maxi<a[i])
maxi=a[i],pos=i;
}
for(int i=1;i<=n;i++)
pre[i]=max(pre[i-1],a[i]);
f.build(1,1,n);
for(int i=1;i<=n;i++)
pre[i]=max(pre[i-1],a[n-i+1]);
g.build(1,1,n);
while(m--){
int op,l,r,x,v;
cin>>op;
if(op==1)
cout<<f.getsum(1,1,n,pos)+g.getsum(1,1,n,n-pos)<<'\n';
else if(op==2){
cin>>l>>r>>v;
if(v>maxi){
cout<<"0\n";
continue;
}
l=max(l,f.getfir(1,1,n,v));
r=min(r,n-g.getfir(1,1,n,v)+1);
cout<<(l<=r?r-l+1:0)<<'\n';
}
else{
cin>>x>>v;
a[x]+=v;
if(maxi<a[x])
maxi=a[x],pos=x;
f.upd(1,1,n,x,a[x]);
g.upd(1,1,n,n-x+1,a[x]);
}
}
return 0;
}
总结:
- 画图、看见式子推式子。
结语
成绩:60+0+0+32=92。
问题:离散化没有注意把所有的元素放进去;对于一些套路不熟悉;草稿纸没有利用好。
方案:注意代码细节,其他同上总结。

浙公网安备 33010602011771号