2025牛客寒假算法基础集训营补题

2025牛客寒假算法基础集训营1

H 井然有序之窗

做法:贪心

先把区间按左端点排序,对于数 i 该放在哪个位置,则将所有左端点小于 i 的区间放入优先队列,取出右端点最小的区间为 i 放的位置。

无解情况:当该右端点最小的区间的右端点小于 i 时无解,当区间未放满时无解。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005;

LL n,m,t,cnt=1,l=1,ans[N];
struct node{
    LL l,r,i;

    bool operator < (const node& a) const {
        return r>a.r;
    }

}a[N],b;
priority_queue<node> q;

inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*w;
}

bool cmp(node a, node b){
    if(a.l==b.l) return a.r-a.l<b.r-b.l;
    else return a.l<b.l;
}

int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i].l=read(),a[i].r=read(),a[i].i=i;
    }
    sort(a+1,a+1+n,cmp);
    while(a[cnt].l<=l&&cnt<=n){
        q.push(a[cnt++]);
    }
    while(!q.empty()){
        b=q.top();q.pop();
        if(b.r<l){
            printf("-1\n");return 0;
        }
        ans[b.i]=l;
        l++;
        while(a[cnt].l<=l&&cnt<=n){
            q.push(a[cnt++]);
        }
    }
    if(cnt<=n){
        printf("-1\n");
    }else{
        for(int i=1;i<=n;i++){
            printf("%lld ",ans[i]);
        }
    }
    return 0;
}

F 双生双宿之探

做法:双指针、前缀和

因为一个合法区间只含有两个数,故可将其变为 1 与 -1

1 4 1 1 4 3 3 4 2 2
1 -1 1 1 -1 1 1 -1 1 1

之后使用前缀和 pre ,再用双指针移动,对于此时区间数种类记为 dist ,因为当且仅当 pre[r]pre[l1]=0 时该区间为双生数组,故统计 cnt[pre[l]] 的数量,在确定一个只含有2个数的长区间时,ans+=cnt[pre[r]]

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005;

LL n,m,t;

inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*w;
}

void solve(){
    n=read();
    vector<LL> a(n),v(n),f(n+1),k(n);
    for(int i=0;i<n;i++){
        v[i]=a[i]=read();
    }
    sort(v.begin(),v.end());
    unique(v.begin(),v.end());
    for(int i=0;i<n;i++){
        a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin();
    }//离散化,用桶统计元素种类
    f[0]=1;
    for(int i=1;i<n;i++){
        if(a[i]==a[i-1]){
            f[i]=f[i-1];
        }else{
            f[i]=-f[i-1];
        }
    }
    LL dist=0,ans=0;
    vector<LL> pre(n+1),cnt(2*n+1);
    pre[0]=n;
    for(int i=1;i<n;i++){
        pre[i+1]=pre[i]+f[i];
    }//前缀和加n,方便计数
    for(int l=0,r=1;r<=n;r++){
        cnt[pre[r-1]]++;
        dist+=(k[a[r-1]]++==0);
        while(dist>2){
            cnt[pre[l]]--;
            dist-=(k[a[l]]--==0);
            l++;
        }
        ans+=cnt[pre[r]];
    }
    printf("%lld\n",ans);
    return ;
}

int main(){
    t=read();
    while(t--){
        solve();
    }
    return 0;
}

K 硝基甲苯之魇

做法:数论,线段树,ST表,二分,map

对于区间 gcd 有这样一个性质,则当左端点 l 确定时,右端点 r 从左向右移动时 gcd 大小是递减的。

20 8 4 6 2 3 5 6 7 5 a[]
20 4 4 2 2 1 1 1 1 1 gcd

所以当 l 固定时我们可以二分找到 gcd 的分界点,可以使用线段树或ST表来算出区间 gcd ,这样再找与该区间 gcd 相等的区间异或和。

对于区间异或和,我们可以通过异或前缀和来计算。

fl,r=pre[r]pre[l1]

在查找这一段区间 gcd 有没有相等的区间异或和时。

pre[r]pre[l1]=gcdl,r

gcdl,rpre[l1]=pre[r]

因为 l 是固定的,所以我们只需要找到这段区间的异或前缀和等于 pre[r] 的个数。

我们可以通过 mapvector 来计算个数,将异或前缀和作为 key , 将位置 i 作为值放入 vector 中,之后通过二分查找位置再相减。

#include<bits/stdc++.h>
#define LL int
#define l(p) tree[p].l
#define r(p) tree[p].r
#define gd(p) tree[p].gd
using namespace std;
const int N=200005;
LL gcd(LL a,LL b);

LL n,m,t,k,a[N];
struct Segment_tree{
    LL l,r,gd;
}tree[N*4];

inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*w;
}

LL gcd(LL a,LL b){
    return b?gcd(b,a%b):a;
}

void pushup(LL p){
    gd(p)=gcd(gd(p<<1),gd(p<<1|1));
    return ;
}

void build(LL p,LL l,LL r){
    l(p)=l,r(p)=r;
    if(l==r){
        gd(p)=a[l];
        return ;
    }
    LL mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    pushup(p);
    return ;
}

LL query(LL p,LL l,LL r){
    if(l(p)>=l&&r(p)<=r){
        return gd(p);
    }
    LL mid=(l(p)+r(p))>>1;
    if(r<=mid) return query(p<<1,l,r);
    else{
        if(l>mid) return query(p<<1|1,l,r);
        else{
            LL val=gcd(query(p<<1,l,r),query(p<<1|1,l,r));
            return val;
        }
    }
    return 0;
}

void solve(){
    n=read();
    vector<LL> pre(n+1);
    unordered_map<LL,vector<LL>> mp;//注意这里因为常数过大,使用map时会超时,而使用unordered_map则不会
    for(int i=1;i<=n;i++){
        a[i]=read();
        pre[i]=pre[i-1]^a[i];
        mp[pre[i]].push_back(i);
    }
    build(1,1,n);
    LL ans=0;
    for(int i=1;i<=n;i++){
        LL L=i,g=a[i];
        while(L<=n){
            
            LL l=L,r=n;
            while(l<r){
                LL mid=(l+r+1)>>1;
                if(query(1,i,mid)==g) l=mid;
                else r=mid-1;
            }
            
            LL d=g^pre[i-1];
            if(L!=i)
            ans+=upper_bound(mp[d].begin(),mp[d].end(),r)-lower_bound(mp[d].begin(),mp[d].end(),L);
            else ans+=upper_bound(mp[d].begin(),mp[d].end(),r)-upper_bound(mp[d].begin(),mp[d].end(),L);
            L=r+1;
            g=query(1,i,r+1);
        }
    }
    printf("%d\n",ans);
    return ;
}

int main(){
    t=read();
    while(t--){
        solve();
    }
    return 0;
}

2025牛客寒假算法基础集训营2

E 一起走很长的路!

做法:前缀和,线段树

分析可知每次操作只增加第 l 块牌的重量为最优操作。

对于需要操作多少次,则通过预处理先求出。

b[i]=a[i]pre[i1]

再通过线段树维护数组 b 的区间最大值。

对于每次询问则找到数组 b[l+1,r] 之中的最大值加上 pre[l1] 则为需要操作的次数。

#include<bits/stdc++.h>
#define LL long long
#define l(p) tree[p].l
#define r(p) tree[p].r
#define ma(p) tree[p].ma
using namespace std;
const int N=200005;

LL n,m,t,k,a[N],pre[N],q,b[N];
struct Segment_tree{
    LL l,r,ma;
}tree[N<<2];

inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*w;
}

void pushup(LL p){
    ma(p)=max(ma(p<<1),ma(p<<1|1));
    return ;
}

void build(LL p,LL l,LL r){
    l(p)=l,r(p)=r,ma(p)=-9223372036854775808;
    if(l==r){
        ma(p)=b[l];
        return ;
    }
    LL mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    pushup(p);
    return ;
}

LL query(LL p,LL l,LL r){
    if(l(p)>=l&&r(p)<=r){
        return ma(p);
    }
    LL mid=(l(p)+r(p))>>1;
    if(r<=mid) return query(p<<1,l,r);
    else
    if(l>mid) return query(p<<1|1,l,r);
    else
    return max(query(p<<1,l,r),query(p<<1|1,l,r));
}

void solve(){
    n=read(),q=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        pre[i]=pre[i-1]+a[i];
        b[i]=a[i]-pre[i-1];
    }
    build(1,1,n);
    for(int i=1;i<=q;i++){
        LL l=read(),r=read();
        if(l==r){
            printf("0\n");
            continue;
        }
        LL x=query(1,l+1,r);
        x+=pre[l-1];
        printf("%lld\n",max(x,0ll));
    }
    return ;
}

int main(){
    t=1;//t=read();
    while(t--){
        solve();
    }
    return 0;
}

2025牛客寒假算法基础集训营3

G 智乃与模数

做法:整除分块,二分

整除分块结论

对于常数 n ,使得式子

ni=nj

成立且满足 ijnj 值最大为 nni ,即值ni 所在块的右端点为 nni

而后对于每一个块内,Nmodi 呈现等差数列 ai=a1+(i1)b 其中 a1=Nmodi,b=N/i

我们可以二分第 k 大的余数 mid ,每次统计所有大于 mid 的数量,并通过等差求和算出答案。

#include<bits/stdc++.h>
#define LL long long
#define l(p) tree[p].l
#define r(p) tree[p].r
#define ma(p) tree[p].ma
using namespace std;
const int N=200005;

LL n,m,t,k;

inline LL read(){
    LL s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*w;
}

void solve(){
    n=read(),k=read();
    LL L=0,R=n,ans=0;
    while(L<R){
        LL mid=(L+R)>>1;
        LL sum=0,a,b,x,tot=0,val=0;
        for(int l=1,r;l<=n;){
            r=n/(n/l);
            a=n%l;
            b=n/l;
            x=0;
            if(a>=mid){
                x=ceil((a-mid)/b)+1;
                if(a-(x-1)*b==mid){
                    val++;
                }
            }
            sum+=x;
            tot+=(a+a-(x-1)*b)*(x)/2;
            l=r+1;
        }
        if(sum==k){
            printf("%lld\n",tot);
            return ;
        }
        if(sum>k){
            if(sum-val<k){
                printf("%lld\n",tot-(sum-k)*mid);
                return ;
            }
        }
        if(sum>k) L=mid+1;
        else R=mid;
    }
    return ;
}

int main(){
    t=1;//t=read();
    while(t--){
        solve();
    }
    return 0;
}
posted @   xingke233  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示