9月17日数据结构专题考试题解(待更新)

9月17日数据结构专题考试题解

$ By~wch $



第一题:加法(线段树)

题目描述:

给一个 $ n $ 阶排列 $ b $ ,要求维护一个初值全为 $ 0 $ 的数组 $ a_i $ ,支持 $ q $ 次如下操作:

  1. 给出 $ l,r $ ,将 $ a_l,a_{l+1}...a_{r-1},a_{r} $ 全部加 \(1\) 。
  2. 给出 $ l,r $ ,查询 $ \sum_{i=l}^{r} \lfloor \frac{a_i}{b_i} \rfloor $

数据范围: $ n\leq 3\times 10^5$


$ solution: $

很让人头疼的一道题目,当时觉得第一个操作肯定是一个突破口,因为每次只加 $ 1 $ 。这是一个很好的性质,在结合一下题目的条件( $ b $ 是个排列!),于是就算了一下最坏情况(不断区间加)的 $ \sum a_i =4\times 10^6 $ ,线段树每一次修改复杂度为 $ logn $ ,两者一乘复杂度可以接受。于是边只要看能不能实现这棵线段树了。

用线段树记录区间总和,用懒标记记录修改,然后每次加入时判断是否有元素达到了 $ b_i $ 权值需要加一,如果有就 $ pushdown $ 操作往下传,一直到那个要修改的叶子节点。(实现过程有点复杂,大家可以自己想,但是我自己都觉得常数可能过不了)


$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int
#define zuo k<<1,l,mid
#define you k<<1|1,mid+1,r
#define midd int mid=(l+r)>>1

using namespace std;

int n,q,sx,sy,sv;
int mi[300005<<2];
int da[300005<<2];
int lz[300005<<2];
bool yz[300005<<2];

inline ll qr(){
    register char ch; bool sign=0; register ll res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

inline void build(int k,int l,int r){
    if(l==r){yz[k]=1; mi[k]=qr(); return ;}
    midd; build(zuo); build(you);
    mi[k]=min(mi[k<<1],mi[k<<1|1]);
}

inline void push(int k){
    if(yz[k]){ //到达叶子节点,直接改
        da[k]+=lz[k]/mi[k]; //加多少
        lz[k]%=mi[k]; //还剩多少
        return ;
    }
    rg l=k<<1,r=k<<1|1;
    lz[l]+=lz[k]; lz[r]+=lz[k];
    if(lz[l]>=mi[l]) push(l); //一直到需要更改的叶子节点
    if(lz[r]>=mi[r]) push(r);
    mi[k]=min(mi[l]-lz[l],mi[r]-lz[r]); //维护还需要多少次加操作达到上界
    da[k]=da[l]+da[r]; lz[k]=0; //记得清零
}

inline void add(int k,int l,int r){
    if(sx<=l&&r<=sy) ++lz[k];
    else { midd; if(lz[k])push(k); //标记下放
        if(sx<=mid) add(zuo);
        if(sy>mid) add(you);
    }
    if(l!=r)mi[k]=min(mi[k<<1]-lz[k<<1],mi[k<<1|1]-lz[k<<1|1]); 
    if(lz[k]>=mi[k]) push(k); //mi数组:最少还需要多少次加操作,就会有元素达到上界
    if(l!=r)da[k]=da[k<<1]+da[k<<1|1]; //区间和
}

inline void ask(int k,int l,int r){
    if(sx<=l&&r<=sy){sv+=da[k]; return ;}
    midd;
    if(sx<=mid) ask(zuo);
    if(sy>mid) ask(you);
}

int main(){
    n=qr(); q=qr(); build(1,1,n);
    for(rg i=1;i<=q;++i){
        rg op=qr(); sx=qr(),sy=qr();
        if(op==1) add(1,1,n);
        else sv=0,ask(1,1,n);
        if(op==2)printf("%d\n",sv);
    }
    return 0;
}


第二题:区间(并查集+计数)

题目描述:

给出一个长度为 $ n $ 的序列 $ a_i $ ,选出两个不相交的区间 $ [ l_1,r_1 ] $ 和 $ [l_2,r_2] $ ,获得的收益是两个区间的区间最小值的最小值。求所有不同的选法的收益之和模 \(10^9+7\) 。

数据范围:$ n\leq 10^6 , a_i \leq 10^9 $


$ solution: $


$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int

using namespace std;

const int mod=1e9+7;

int n,tot,ans;
int s[1000005];
int t[1000005];
int r[1000005];
int g[1000005];
int f[1000005];

struct su{
    int v,id;
    inline bool operator <(const su &i)const{
        return v>i.v;
    }
}a[1000005];

inline int qr(){
    register char ch; register bool sign=0; rg res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

inline int get(const int &x){
    if(x==s[x])return x;
    return s[x]=get(s[x]); //路径压缩
}

inline void bin(int x,int y){
    x=get(x); y=get(y);
    if(t[x]>t[y])swap(x,y); //按秩合并
    tot-=g[t[x]]+g[t[y]];
    t[y]+=t[x]; s[x]=y; r[y]=max(r[x],r[y]);
    tot+=g[t[y]];
    while(tot<0) tot+=mod;
    while(tot>=mod) tot-=mod;
}

int main(){
    n=qr();
    for(rg i=1;i<=n;++i) g[i]=(g[i-1]+i)%mod;
    for(rg i=1;i<=n;++i) f[i]=(g[i]+f[i-1])%mod;
    for(rg i=1;i<=n;++i) a[i]=su{qr(),i};
    sort(a+1,a+n+1);
    for(rg i=1;i<=n;++i){
        rg k=a[i].id; ++tot;
        s[k]=k; t[k]=1; r[k]=k;
        if(s[k-1]) bin(k-1,k);
        if(s[k+1]) bin(k,k+1);
        rg j=get(k),y=r[j]-k,x=t[j]-y-1,v=(tot-g[t[j]]+mod)%mod,tt=(ll)(x+1)*(y+1)%mod;
        // 区间编号 右边长度 左边长度    其他区间可组成的片段个数 这个区间里包含k的片段数       
        (ans+=(ll)a[i].v*(((ll)(x+1)*f[y]+(ll)(y+1)*f[x]+(ll)tt*v)%mod)%mod)%=mod;
        //        权值     左边片段贡献    右边片段贡献   其他区间贡献
    }
    printf("%d\n",ans);
    return 0;
}


第三题:序列(可持久化trie+特殊性质)


$ solution: $

加班中…….


$ code: $



第四题:玉蟾宫(悬线法)

题目链接:洛谷 P4147 玉蟾宫


$ solution: $

已经写过题解了,在写悬线法详解的时候讲的。

挂个链接:最大子矩阵的两种求法


$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int

using namespace std;

int n,m,ans;
int a[1005][1005];
int l[1005][1005];
int r[1005][1005];
int h[1005][1005];

inline ll qr(){
    register char ch; bool sign=0; register ll res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

int main(){
    n=qr(); m=qr(); char ch;
    for(rg i=1;i<=n;++i){
        for(rg j=1;j<=m;++j){
            while((ch=getchar())!='F'&&ch!='R');
            a[i][j]=(ch=='F');
            if(a[i][j]){
                l[i][j]=l[i][j-1]+1; //向左延伸
                h[i][j]=h[i-1][j]+1; //向上延伸
            }
        }
    }
    for(rg i=1;i<=n;++i)
        for(rg j=m;j>=1;--j)
            if(a[i][j])r[i][j]=r[i][j+1]+1; //向右延伸
    for(rg i=1;i<=n;++i){
        for(rg j=m;j>=1;--j){
            if(a[i-1][j])continue;
            rg x=1e9,y=1e9;
            for(rg k=i;a[k][j]&&k<=n;++k){
                if(l[k][j]<x)x=l[k][j];
                if(r[k][j]<y)y=r[k][j];
                ans=max(ans,(x+y-1)*(k-i+1));
            }
        }
    }
    printf("%d\n",ans*3);
    return 0;
}


第五题:Eden 的新背包问题

题目链接:洛谷 P4095 [HEOI2013] Eden 的新背包问题


$ solution: $


$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int

using namespace std;

int n,m,q;
int a[1005];
int b[1005];
int c[1005];
int s[1005][10005];
int k[1005][10005];
int qv[10005];
int qt[10005];

inline int qr(){
    register char ch; register bool sign=0; rg res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

inline void dp(int f[],int w,int v,int t){ //讲一个物品加入背包
    for(rg i=0;i<w;++i){ 
        rg l=1,r=0; qv[++r]=f[i]; qt[r]=0; //单调队列优化
        for(rg j=i+w;j<=m;j+=w){
            rg y=j/w,x=f[j]-y*v;
            while(l<=r&&y-qt[l]>t)++l;
            f[j]=max(f[j],y*v+qv[l]); //蓝书上有讲
            while(l<=r&&qv[r]<=x)--r;
            qv[++r]=x; qt[r]=y;
        }
    }
}

int main(){
    n=qr();  m=1e4;
    for(rg i=1;i<=n;++i)
        a[i]=qr(),b[i]=qr(),c[i]=qr();
    q=qr();
    for(rg i=1;i<=n;++i){
        for(rg j=0;j<=m;++j)
            s[i][j]=s[i-1][j]; //记录每一步过程
        dp(s[i],a[i],b[i],c[i]); //做一遍前缀
    }
    for(rg i=n;i>=1;--i){
        for(rg j=0;j<=m;++j)
            k[i][j]=k[i+1][j]; //记录每一步过程
        dp(k[i],a[i],b[i],c[i]); //做一遍后缀
    }
    for(rg i=1;i<=q;++i){
        rg x=qr(),y=x+2,v=qr(),ans=0;
        for(rg j=0;j<=v;++j) //找到前后两个位置,合并
            ans=max(ans,s[x][j]+k[y][v-j]);
        printf("%d\n",ans);
    }
    return 0;
}
上一篇:1005 Spell It Right (20 分)


下一篇:1005 Spell It Right (20 分)