数据结构专题总结

打了一天的数据结构,感觉码力上升的很快,而且也学会了许多方法,但总体来说今天大部分的题很多都是看完题解以后才会的,无论怎么想也想不出来,还是要提高一下想题的能力,不要走神,不要急躁,仔细思考,不要颓废,还是要抓紧数据结构题的暴力,一般都很多,所以想不出正解一定要打暴力啊。

\(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\) 学长的题解,挂一个学长题解的链接。

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;
}
posted @ 2022-11-03 21:54  hxqasd  阅读(51)  评论(5编辑  收藏  举报