数据结构专题总结
打了一天的数据结构,感觉码力上升的很快,而且也学会了许多方法,但总体来说今天大部分的题很多都是看完题解以后才会的,无论怎么想也想不出来,还是要提高一下想题的能力,不要走神,不要急躁,仔细思考,不要颓废,还是要抓紧数据结构题的暴力,一般都很多,所以想不出正解一定要打暴力啊。
\(A\)
求区间 \(lcm\)。
对于 \(lcm\) 的数学性质的分析,我们发现我们只需要对于一个数每一个质因子(质数,质数的次幂在此均算质因子)在之前的出现位置上除以一个他的质数,(即在形如 \(p^k\) 的上一次出现的位置除去一个 \(p\)),就可以保证将重复的部分除去了(设 \(i<j\),如果 \(p^k \mid a_i \land p^{k+1} \mid a_j\),那么我们将在处理到 \(j\) 时,对于 \(i\) 位置的 \(p\) 的 \([1,k]\) 次方,除去 \(k\) 个 \(p\),我们再将 \(p^{k+1}\) 插入 \(j\) 位置,这样保证当区间左端点小于 \(i\) 时,即我们要求的 \(lcm\) 的元素包含 \(a_i\) 时,可以除去多余元素,当不包含时,保留这部分答案),那么我们只需要处理 \([l,r]\) 的积即可,我们发现如果离线,我们可以处理到区间右端点时统计答案,但本题强制在线,所以我们用主席树来维护。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define lson(rt) (tree[rt].ls)
#define rson(rt) (tree[rt].rs)
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
int root[N];
int pos[N<<1];
int a[N],n,Q;
long long ans=0;
struct Prisident_Tree{
struct Pri{
int ls,rs,sum;
Pri(){ls=0,rs=0,sum=1;}
}tree[40000000];
int tot;
void pushup(int rt){tree[rt].sum=(1ll*tree[lson(rt)].sum*tree[rson(rt)].sum%mod);}
int insert(int rt,int l,int r,int pos,long long val){
int k=++tot;
tree[k]=tree[rt];
if(l==r){
tree[k].sum=1ll*tree[rt].sum*val%mod;
return k;
}
int mid=(l+r)>>1;
if(pos<=mid)lson(k)=insert(lson(rt),l,mid,pos,val);
else rson(k)=insert(rson(rt),mid+1,r,pos,val);
pushup(k);
return k;
}
long long ask(int rt,int l,int r,int pos){
if(l>=pos)return 1ll*tree[rt].sum;
if(r<pos)return 1;
int mid=(l+r)>>1;
return 1ll*ask(lson(rt),l,mid,pos)*ask(rson(rt),mid+1,r,pos)%mod;
}
}T;
int prime[N<<1],not_prime[N<<1],min_prime[N<<1];
long long ny[N<<1];
void init(){
ny[1]=1;
for(int i=2;i<=200000;i++)ny[i]=1ll*(mod-mod/i)*ny[mod%i]%mod;
not_prime[1]=1;
min_prime[1]=0;
for(int i=2;i<=200000;i++){
if(!not_prime[i])prime[++prime[0]]=i,min_prime[i]=i;
for(int j=1;j<=prime[0];j++){
if(i*prime[j]>200000)break;
int k=i*prime[j];
not_prime[k]=1;
min_prime[k]=prime[j];
if(i%prime[j]==0)break;
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
init();
for(int i=1;i<=n;i++){
root[i]=root[i-1];
while(min_prime[a[i]]){
int k=min_prime[a[i]],t=1;
while(a[i]%k==0){
t*=k,a[i]/=k;
if(pos[t])root[i]=T.insert(root[i],1,n,pos[t],ny[k]);
pos[t]=i;
}
root[i]=T.insert(root[i],1,n,i,t);
}
}
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
s1=(s1+ans)%n+1,s2=(s2+ans)%n+1;
if(s1>s2)swap(s1,s2);
ans=T.ask(root[s2],1,n,s1)%mod;
printf("%lld\n",ans);
}
return 0;
}
$B $
较为不可做的题,一道 \(kruskal\) 重构树板子题,具体思路就是将每个边的编号转成边权,然后跑 \(kruskal\) 重构树将连接两个点 \(u,v\) 的 的最大边权转成 \(u,v\) 在 \(kruskal\) 重构树上的点权,我们只要求出相邻的两个点 \(i,i+1\) 的代价 \(f(i)\),对于 \([l,r]\) 区间查询 \(\max_{i=l}^{r-1} f(i)\) 即可。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=3e5+10;
struct Edge{
int u,v,w;
}edge[N<<1];
bool cmpval(Edge a,Edge b){return a.w<b.w;}
vector<int>G[N];
int n,m,q;
int fa[N];
int val[N];
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
struct Tree_Cut{
int siz[N],fa[N],top[N],son[N],dep[N];
void get_heavy_son(int x,int f,int depth){
siz[x]=1,fa[x]=f,dep[x]=depth,son[x]=0;
for(auto v:G[x]){
if(v==f)continue;
get_heavy_son(v,x,depth+1);
siz[x]+=siz[v];
if(siz[v]>siz[son[x]])son[x]=v;
}
}
void get_heavy_edge(int x,int tp){
top[x]=tp;
if(son[x])get_heavy_edge(son[x],tp);
for(auto v:G[x]){
if(v==fa[x]||v==son[x])continue;
get_heavy_edge(v,v);
}
}
int lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
}T;
struct Segment_Tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
struct Seg{
int maxk;
}tree[N<<2];
void pushup(int rt){tree[rt].maxk=max(tree[lson].maxk,tree[rson].maxk);}
void build(int rt,int l,int r){
if(l==r){
tree[rt].maxk=val[T.lca(l,l+1)];
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
int ask(int rt,int l,int r,int L,int R){
if(l<=L&&R<=r)return tree[rt].maxk;
int mid=(L+R)>>1;
int ans=0;
if(l<=mid)ans=max(ans,ask(lson,l,r,L,mid));
if(r>mid)ans=max(ans,ask(rson,l,r,mid+1,R));
return ans;
}
}Tr;
void build(){
int cnt=n;
for(int i=1;i<2*n;i++)fa[i]=i;
sort(edge+1,edge+1+m,cmpval);
for(int i=1;i<=m;i++){
if(find(edge[i].u)==find(edge[i].v))continue;
int x=find(edge[i].u),y=find(edge[i].v),z=++cnt;
val[z]=edge[i].w;
fa[x]=z,fa[y]=z;
G[x].push_back(z),G[y].push_back(z);
G[z].push_back(x),G[z].push_back(y);
}
int root=find(1);
T.get_heavy_son(root,0,1);
T.get_heavy_edge(root,root);
Tr.build(1,1,n-1);
for(int i=1;i<=cnt;i++)G[i].clear(),val[i]=0,fa[i]=0,T.siz[i]=0,T.fa[i]=0,T.son[i]=0,T.dep[i]=0,T.top[i]=0;
}
void work(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++){
scanf("%d%d",&edge[i].u,&edge[i].v);
edge[i].w=i;
}
build();
for(int i=1;i<=q;i++,putchar(' ')){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
if(s1==s2)putchar('0');
else{
s2--;
printf("%d",Tr.ask(1,s1,s2,1,n-1));
}
}
}
int main(){
int t=0;
scanf("%d",&t);
while(t--)work(),putchar('\n');
return 0;
}
\(C\)
这道题是一道典型的偏序问题,通过题意转化我们可以将其转化为一道二维偏序问题。
首先我们发现,对于每个人当队长时,他队伍里的人是一定的,不会改变的,所以我们先预处理出这部分答案,用树状数组维护二维偏序扫一遍即可,然后我们再来考虑询问,我们发现本题不强制在线,这也就启发我们可以通过离线算法来做,首先我们剔除不合法的询问,也就是说剔除 \(x,y\) 的 \(r\) 相距超过 \(2k\) 的询问。然后我们假设 \(a_x<a_y\),我们发现一个 \(z\) 能成为 \(x,y\) 的队长,当且仅当 \(r_z>max(r_x,r_y)\) 且 \(a_z\in[a_y-k,a_x+k]\),所以我们就可以将询问按 \(max(r_x,r_y)\) 从大到小排序,利用线段树在 \([a_y-k,a_x+k]\) 范围内找满足条件的能管辖最多人数的队长即可。
当然,本题由于数据范围过大,我们需要离散化。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
int n,k;
int lsha[N],cnta,lena;
int lshr[N],cntr,lenr;
struct People{int r,a;}p[N];
int id[N],have[N];
bool cmpr(int x,int y){return p[x].r>p[y].r;}
bool cmpa(int x,int y){return p[x].a>p[y].a;}
struct Ask{
int x,y,id;
}ask[N];
int tot;
int ans[N];
bool cmpask(Ask a,Ask b){return max(p[a.x].r,p[a.y].r)>max(p[b.x].r,p[b.y].r);}
struct Bit{
int c[N],lim;
int lowbit(int x){return x&(-x);}
void add(int x,int k){while(x<=lim){c[x]+=k,x+=lowbit(x);}}
int getsum(int x){int ans=0;while(x){ans+=c[x],x-=lowbit(x);}return ans;}
}Tr;
struct Segment_Tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
struct Seg{
int maxk;
}tree[N<<4];
void pushup(int rt){
tree[rt].maxk=max(tree[lson].maxk,tree[rson].maxk);
}
void update(int rt,int l,int r,int pos,int val){
if(l==r){
tree[rt].maxk=max(tree[rt].maxk,val);
return ;
}
int mid=(l+r)>>1;
if(pos<=mid)update(lson,l,mid,pos,val);
else update(rson,mid+1,r,pos,val);
pushup(rt);
}
int ask(int rt,int l,int r,int L,int R){
if(l<=L&R<=r)return tree[rt].maxk;
int mid=(L+R)>>1;
int ans=0;
if(l<=mid)ans=max(ans,ask(lson,l,r,L,mid));
if(r>mid)ans=max(ans,ask(rson,l,r,mid+1,R));
return ans;
}
}T;
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&p[i].r),lshr[++cntr]=p[i].r,id[i]=i;
for(int i=1;i<=n;i++)scanf("%d",&p[i].a),lsha[++cnta]=p[i].a,lsha[++cnta]=p[i].a-k,lsha[++cnta]=p[i].a+k;
sort(lshr+1,lshr+1+cntr),sort(lsha+1,lsha+1+cnta);
lenr=unique(lshr+1,lshr+1+cntr)-lshr-1;
lena=unique(lsha+1,lsha+1+cnta)-lsha-1;
for(int i=1;i<=n;i++)p[i].r=lower_bound(lshr+1,lshr+1+lenr,p[i].r)-lshr;
sort(id+1,id+1+n,cmpa);
int l=1,r=0;
Tr.lim=lenr;
for(int i=1;i<=n;i++){
while(r+1<=n&&p[id[r+1]].a>=p[id[i]].a-k)r++,Tr.add(p[id[r]].r,1);
while(l<=n&&p[id[l]].a>p[id[i]].a+k)Tr.add(p[id[l]].r,-1),l++;
have[id[i]]=Tr.getsum(p[id[i]].r);
}
int Q=0;
scanf("%d",&Q);
for(int i=1;i<=Q;i++){
int s1=0,s2=0;
scanf("%d%d",&s1,&s2);
if(abs(p[s2].a-p[s1].a)>2*k)continue;
if(p[s1].a>p[s2].a)swap(s1,s2);
ask[++tot].x=s1,ask[tot].y=s2,ask[tot].id=i;
}
sort(ask+1,ask+1+tot,cmpask);
sort(id+1,id+1+n,cmpr);
l=0;
for(int i=1;i<=tot;i++){
int limit=max(p[ask[i].x].r,p[ask[i].y].r);
while(l+1<=n&&p[id[l+1]].r>=limit){
l++;
int pos=lower_bound(lsha+1,lsha+1+lena,p[id[l]].a)-lsha;
T.update(1,1,lena,pos,have[id[l]]);
}
int L=lower_bound(lsha+1,lsha+1+lena,p[ask[i].y].a-k)-lsha;
int R=lower_bound(lsha+1,lsha+1+lena,p[ask[i].x].a+k)-lsha;
ans[ask[i].id]=T.ask(1,L,R,1,lena);
}
for(int i=1;i<=Q;i++)ans[i]!=0?printf("%d\n",ans[i]):puts("-1");
return 0;
}
\(D\)
这题我觉得不管我写不写,但我还是要夸一下洛谷上这题的第一篇题解,如果你仔细看完,你就会发现讲的真是通俗易懂,那语文素养是真的高。
关于本题,因为有异或,所以我们考虑将问题转化到 \(Trie\) 上搞,我们发现删除几条边很难处理,但留下几条边是相对容易的,所以我们将问题转化为最多留下几条边是这个图为一棵树。
我们观察发现,对于图是一棵树的情况,当恰有一对 \((a_i,a_j)\) 互为其异或起来的最小值,所以我们考虑找到这个最小值。
我们考虑先建好 \(Trie\) 然后在 \(Trie\) 上 \(dp\),我们考虑如何转移。
当一个节点为叶子节点时,他的答案为 \(1\)。
当一个节点没有左儿子或右儿子时,他的值即为它存在的那个儿子的值。
当一个节点有左儿子和右儿子时,他的值为 他左儿子的值与他右儿子的值取 \(max\) 后加 \(1\),因为我们选了一边最多在另一边选一个。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=7e6+10;
struct Trie{
int tree[N][2];
int tot;
void insert(int x){
int rt=0;
for(int i=29;i>=0;i--){
int s=((x>>i)&1);
if(!tree[rt][s])tree[rt][s]=++tot;
rt=tree[rt][s];
}
}
int dfs(int x){
if(!tree[x][0]&&!tree[x][1])return 1;
if(!tree[x][0])return dfs(tree[x][1]);
if(!tree[x][1])return dfs(tree[x][0]);
return max(dfs(tree[x][0]),dfs(tree[x][1]))+1;
}
}T;
int main(){
int n=0;
scanf("%d",&n);
for(int i=1,s1=0;i<=n;i++)scanf("%d",&s1),T.insert(s1);
printf("%d\n",n-T.dfs(0));
return 0;
}
\(F\)
我们发现本题和之前的一道 \(pyramid\) 的思路很类似,都是二分,但两道题在做法上很不同。
我们考虑二分答案,我们设当前所二分的答案为 \(mid\),那么用类似 \(pyramid\) 的思想,将大于等于 \(mid\) 的数为 \(1\),小于 \(mid\) 的数为 \(0\),那么对于每个询问,我们只需要找在当前所二分的答案下,在 \([l,r]\) 区间中,最长的连续的一段 \(1\) 的长度是否大于等于 \(k\) 即可,由于每个 \(mid\) 的树都不一样,我们考虑使用主席树来维护。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define lson(rt) (tree[rt].ls)
#define rson(rt) (tree[rt].rs)
using namespace std;
const int N=2e5+10;
int n,q,a[N],id[N],lsh[N],cnt,len,root[N];
bool cmp(int x,int y){return a[x]<a[y];}
struct President_Tree{
struct Seg{
int ls,rs,pre,suf,len,length;
Seg(){ls=0,rs=0,pre=0,suf=0,len=0,length=0;}
}tree[N*50];
int tot;
inline friend Seg operator + (Seg a,Seg b){
Seg c;
c.length=a.length+b.length;
c.len=max(a.len,max(b.len,a.suf+b.pre));
c.pre=((a.pre==a.length)?a.len+b.pre:a.pre);
c.suf=((b.suf==b.length)?a.suf+b.len:b.suf);
return c;
}
void pushup(int rt){
int lls=lson(rt),rrs=rson(rt);
tree[rt]=tree[lson(rt)]+tree[rson(rt)];
lson(rt)=lls,rson(rt)=rrs;
}
int build(int rt,int l,int r){
int k=++tot;
tree[k]=tree[rt];
if(l==r){
tree[k].pre=tree[k].suf=tree[k].len=0;
tree[k].length=1;
return k;
}
int mid=(l+r)>>1;
lson(k)=build(lson(rt),l,mid);
rson(k)=build(rson(rt),mid+1,r);
pushup(k);
return k;
}
int update(int rt,int l,int r,int pos,int val){
int k=++tot;
tree[k]=tree[rt];
if(l==r){
tree[k].pre=tree[k].suf=tree[k].len=val;
tree[k].length=1;
return k;
}
int mid=(l+r)>>1;
if(pos<=mid)lson(k)=update(lson(rt),l,mid,pos,val);
else rson(k)=update(rson(rt),mid+1,r,pos,val);
pushup(k);
return k;
}
Seg ask(int rt,int l,int r,int L,int R){
if(l<=L&&R<=r)return tree[rt];
int mid=(L+R)>>1;
Seg res1,res2;
if(l<=mid)res1=ask(lson(rt),l,r,L,mid);
if(r>mid)res2=ask(rson(rt),l,r,mid+1,R);
return res1+res2;
}
bool check(int l,int r,int k,int val){
Seg tmp;
tmp=ask(root[val],l,r,1,n);
return tmp.len>=k;
}
int getans(int l,int r,int k){
int L=1,R=len,ans=0;
while(L<=R){
int mid=(L+R)>>1;
if(check(l,r,k,mid))L=mid+1,ans=mid;
else R=mid-1;
}
return lsh[ans];
}
}T;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),lsh[++cnt]=a[i],id[i]=i;
sort(id+1,id+1+n,cmp);
sort(lsh+1,lsh+1+cnt);
len=unique(lsh+1,lsh+1+cnt)-lsh-1;
int r=n+1;
root[len+1]=T.build(root[len+1],1,n);
for(int i=len;i>=1;i--){
root[i]=root[i+1];
while(r-1>=1&&a[id[r-1]]==lsh[i]){
r--;
root[i]=T.update(root[i],1,n,id[r],1);
}
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
int s1=0,s2=0,s3=0;
scanf("%d%d%d",&s1,&s2,&s3);
printf("%d\n",T.getans(s1,s2,s3));
}
return 0;
}
\(G\)
感谢 \(Rubyonly\) 学长的题解,挂一个学长题解的链接。
我们使用分块 \(+\) 双端队列来做这道题。
我们发现我们可以对每一个块进行预处理,处理出每个块权值为 \(k\) 的数有多少个,这可以在分块时完成。
考虑区间修改,对于左右端点在同一个块中的情况,我们暴力处理。对于不在同一个块中的情况,我们可以利用双端队列可在队首插入元素的强大性质,在整块之间用 \(O(\sqrt n)\) 的复杂度完成,并用 \(O(\sqrt n)\) 的复杂度暴力重构两边的散块。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;
const int N=1e5+10;
const int M=320;
int n,q,s;
int st[M],ed[M],cnt[M][N],bl[N];
deque<int>buc[M];
int ans;
int a[N];
void init(){
s=sqrt(n);
for(int i=1;i<=s;i++){
st[i]=(n/s)*(i-1)+1,ed[i]=(n/s)*i;
if(i==s)ed[i]=n;
for(int j=st[i];j<=ed[i];j++){
bl[j]=i;
buc[i].push_back(a[j]);
cnt[i][a[j]]++;
}
}
}
int stk[N],top;
void update(int l,int r){
int x=bl[l],y=bl[r];
if(x==y){
for(int i=st[x];i<l;i++)stk[++top]=buc[x].front(),buc[x].pop_front();
for(int i=ed[x];i>=r;i--)stk[++top]=buc[x].back(),buc[x].pop_back();
buc[x].push_front(stk[top--]);
for(int i=ed[x];i>r;i--)buc[x].push_back(stk[top--]);
for(int i=st[x];i<l;i++)buc[x].push_front(stk[top--]);
return ;
}
for(int i=x;i<y;i++){
int tt=buc[i].back();buc[i].pop_back();cnt[i][tt]--;
buc[i+1].push_front(tt),cnt[i+1][tt]++;
}
for(int i=ed[x]-1;i>=l;i--)stk[++top]=buc[x].back(),buc[x].pop_back();
for(int i=ed[y];i>=r;i--)stk[++top]=buc[y].back(),buc[y].pop_back();
cnt[y][stk[top]]--;
cnt[x][stk[top]]++;
buc[x].push_back(stk[top--]);
for(int i=ed[y];i>r;i--)buc[y].push_back(stk[top--]);
for(int i=ed[x]-1;i>=l;i--)buc[x].push_back(stk[top--]);
}
void getans(int l,int r,int k){
int x=bl[l],y=bl[r];
ans=0;
if(x==y){
for(int i=l-st[x];i<=r-st[x];i++)ans+=(buc[x][i]==k);
printf("%d\n",ans);
return ;
}
for(int i=l-st[x];i<buc[x].size();i++)ans+=(buc[x][i]==k);
for(int i=0;i<=r-st[y];i++)ans+=(buc[y][i]==k);
for(int i=x+1;i<=y-1;i++)ans+=cnt[i][k];
printf("%d\n",ans);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
init();
scanf("%d",&q);
for(int i=1;i<=q;i++){
int opt=0,s1=0,s2=0,s3=0;
scanf("%d%d%d",&opt,&s1,&s2);
s1=(s1+ans-1)%n+1,s2=(s2+ans-1)%n+1;
if(s1>s2)swap(s1,s2);
if(opt==1)update(s1,s2);
else scanf("%d",&s3),s3=(s3+ans-1)%n+1,getans(s1,s2,s3);
}
return 0;
}
\(H\)
我觉得这题很好做,如果你做过数位 \(DP\) 的话,你会发现这题与题库中的 \(haha\) 数的转化是一样的,都是利用了模 \(lcm\) 结果不变的性质。
我们首先发现本题的 \(a_i\) 在 \(6\) 以内,我们发现 \(lcm(1,2,3,4,5,6) = 60\),所以我们只需要将时间 $\bmod 60 $ 即可,就可以在线段树上维护了。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int W=60;
const int N=1e5+10;
int n,q,a[N];
char opt[5];
struct Segment_Tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
struct Seg{
int sum[W];
Seg(){memset(sum,0,sizeof(sum));}
bool empty(){for(int i=0;i<W;i++)if(sum[i]!=0)return false;return true;}
}tree[N<<2];
friend Seg operator +(Seg a,Seg b){
Seg c;
for(int i=0;i<W;i++)c.sum[i]=a.sum[i]+b.sum[(i+a.sum[i])%W];
return c;
}
void pushup(int rt){tree[rt]=tree[lson]+tree[rson];}
void build(int rt,int l,int r){
if(l==r){
for(int i=0;i<W;i++)tree[rt].sum[i]=((i%a[l]==0)?2:1);
return ;
}
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(rt);
}
void update(int rt,int l,int r,int pos,int val){
if(l==r){
for(int i=0;i<W;i++)tree[rt].sum[i]=((i%val==0)?2:1);
return ;
}
int mid=(l+r)>>1;
if(pos<=mid)update(lson,l,mid,pos,val);
else update(rson,mid+1,r,pos,val);
pushup(rt);
}
Seg ask(int rt,int l,int r,int L,int R){
if(l<=L&&R<=r)return tree[rt];
Seg res;
int mid=(L+R)>>1;
if(l<=mid)res=ask(lson,l,r,L,mid);
if(r>mid){
Seg tmp;
tmp=ask(rson,l,r,mid+1,R);
res=res+tmp;
}
return res;
}
int getans(int l,int r){
r--;
Seg tmp;
tmp=ask(1,l,r,1,n);
return tmp.sum[0];
}
}T;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
T.build(1,1,n);
scanf("%d",&q);
for(int i=1,s1=0,s2=0;i<=q;i++){
scanf("%s%d%d",opt,&s1,&s2);
if(opt[0]=='A')printf("%d\n",T.getans(s1,s2));
else T.update(1,1,n,s1,s2);
}
return 0;
}
\(J\)
我觉得本题的难度不在数据结构上,而在单调性优化上。
我们观察式子,对于每一个区间,我们要求出最小的 \(|x_i-x_j|\times (w_i+w_j)\)
因为我们的 \(x\) 是单增的,那么我们发现这个式子就变成了 \((x_j-x_i)\times (w_i+w_j)\)
我们发现我们能枚举出的点对有 \(O(n^2)\) 级别,但答案的来源只有 \(O(n)\),因为由于决策单调性,我们只需要找出每个点向左第一个 \(w\) 值小于 \(i\) 的,向右第一个 \(w\) 值小于 \(i\) 的即可,这些点对就可作为答案出现。
解释一下,我们考虑为何只要找向左第一个 \(w\) 值小于 \(i\) 的,向右第一个 \(w\) 值小于 \(i\) 的就行,我们设 \(i<j<k\) 且 \(w_k<w_j<w_i\),我们可能会认为 \((i,k)\) 是有可能比 \((i,j)\) 优的,但由于在这种情况下 \((j,k)\) 一定是比 \((i,k)\) 优的,并且这两种情况的右端点相同,所以我们一定将所有可能最优的答案找到了。
接下来就不难了,先离线,再利用线段树区间求 \(\min\) 即可。
\(code\)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const long long INF=0x3f3f3f3f3f3f3f3f;
const int N=3e5+10;
int stk[N],top,L[N],R[N],n,q,x[N],w[N];
long long ans[N];
typedef pair<int,int> P;
typedef vector<P> V;
typedef vector<int> Vec;
V ask[N];
Vec pos[N];
struct Segment_Tree{
#define lson (rt << 1)
#define rson (rt << 1 | 1)
long long mink[N<<2];
void pushup(int rt){mink[rt]=min(mink[lson],mink[rson]);}
void build(int rt,int l,int r){
mink[rt]=INF;
if(l==r)return;
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
}
void update(int rt,int l,int r,int pos,long long val){
if(l==r){mink[rt]=min(mink[rt],val);return ;}
int mid=(l+r)>>1;
if(pos<=mid)update(lson,l,mid,pos,val);
else update(rson,mid+1,r,pos,val);
pushup(rt);
}
long long ask(int rt,int l,int r,int L,int R){
if(l<=L&&R<=r)return mink[rt];
int mid=(L+R)>>1;
long long ans=INF;
if(l<=mid)ans=min(ans,ask(lson,l,r,L,mid));
if(r>mid)ans=min(ans,ask(rson,l,r,mid+1,R));
return ans;
}
}T;
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)scanf("%d%d",&x[i],&w[i]);
for(int i=1;i<=n;i++){
while(top&&w[stk[top]]>w[i])top--;
L[i]=stk[top];
stk[++top]=i;
if(!L[i])continue;
pos[i].push_back(L[i]);
}
top=0;
for(int i=n;i>=1;i--){
while(top&&w[stk[top]]>w[i])top--;
R[i]=stk[top];
stk[++top]=i;
if(!R[i])continue;
pos[R[i]].push_back(i);
}
top=0;
T.build(1,1,n);
for(int i=1,s1=0,s2=0;i<=q;i++)scanf("%d%d",&s1,&s2),ask[s2].push_back(make_pair(s1,i));
for(int i=1;i<=n;i++){
for(auto l:pos[i])T.update(1,1,n,l,1ll*(x[i]-x[l])*(1ll*w[i]+1ll*w[l]));
for(auto p:ask[i])ans[p.second]=T.ask(1,p.first,i,1,n);
}
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
return 0;
}