补题/6.7~6.8(牛客练习赛140)(ABC408)

真是越来越菜了,牛客已经掉到蓝了(悲
6.7/21:57:喜报,ABC终于能自己A5题了,虽然好像这场前五题比之前简单

C

题意:略

思路:

666,p=1时,只有第一位为1,其余位都是0

void solve(){
    int d,p;cin>>d>>p;
    int k;
    int td=d,tp=p;
    if(d%p==0){
        k=1;
    }else{
        int gcd=__gcd(d,p);
        p/=gcd;
        k=p;
    }
    int q;cin>>q;
    while(q--){
        int l,r;cin>>l>>r;
        if(td==tp){
            cout<<1<<endl;continue;
        }
        if(p==1){
            if(l==1&&r>1)cout<<2<<endl;
            else{
                cout<<1<<endl;
            }
        }
        else cout<<min(r-l+1,k)<<endl;
    }
}

B

题意:

想从1到s,每次向左或向右或跳到n/x的位置,求跳的最小次数

思路:

本来以为是经典BFS,结果又T又M
发现范围为1e18
显然需要数学结论直接推导得到答案
模拟得到当s在大于n/2的位置时,可以先跳到n在往回走,或者跳到n/2处往前走
s在小于n/2时,可以一直往前走,或者跳到s左边某个位置,或者跳到s右边某个位置,然后再往前或往回走

void solve(){
    cin>>n>>s;
    int res=0;
    if(s==1){
        cout<<0<<endl;return;
    }

    if(s<=n/2){
        int pos=n/s+2;
        res=s-1;
        if(pos<=s){
            res=min(res,(n/s-1)+1+abs(n/(n/s)-s));
            res=min(res,(n/s+1-1)+1+abs(n/(n/s+1)-s));
            res=min(res,(n/s-1-1)+1+abs(n/(n/s-1)-s));
        }
    }else{
        int pos=2;
        res=min(n-s+1,pos-1+1+(s-n/2));
    }
    cout<<res<<endl;
}

C

题意:

一个长度为n的初始全0序列,每次可以取序列的mex并将序列的任意一个数换为mex
求转化为给定序列的最小次数(小于等于n次)以及操作

思路:

先考虑无法生成给定序列的情况
1.序列某一个大于0的数出现超过两次,显然当一个数已经在序列中出现,那mex就一定不会是它,因此只能出现一次
2.序列最大值小于等于n,如果大于n,显然操作次数大于n次
3.序列大于0的小于最大值的数有大于等于2个的数没有出现
模拟后发现要么是mex从小至大依次填入。要么是中间某个数没有在序列中出现,此时这个数要先放在最大数的位置上。

void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    vector<int>cnt(1000);
    int mx=0;
    rep(i,1,n){
        cin>>a[i];
        cnt[a[i]]++;
        mx=max(mx,a[i]);
    }
    if(mx>n){
        cout<<-1<<endl;return;
    }
    int k=0;
    rep(i,0,mx){
        if(!cnt[i])k++;
    }
    if(k>=2){
        cout<<-1<<endl;return;
    }
    rep(i,1,mx){
        if(cnt[i]>=2){
            cout<<-1<<endl;return;
        }
    }
    cout<<mx<<endl;
    int mex=1;
    while(mex<=mx){
        if(cnt[mex]){
            rep(i,1,n){
                if(a[i]==mex){
                    cout<<i<<' ';break;
                }
            }
        }else{
            rep(i,1,n){
                if(a[i]==mx){
                    cout<<i<<' ';break;
                }
            }
        }
        mex++;
    }
    cout<<endl;
}

E

题意:

定义一个小写字母s施加一次魔法后变为(char)s+1。当s为z时,施加魔法后变为a
给定一个字符串s,q次查询。维护两种操作
1.对于[l,r]内的字符施加D次魔法
2.查询[l,r]的字符子串是否能通过重排变为回文串

思路:

一个字符串能被重排为回文串的充要条件为:26个字母中最多出现一个奇数次的
(长度为偶数:次数都为偶(显然如果有一个奇次,那么长度就为奇数),长度为奇数:只有一个奇次)
将26个字符进行状压映射,a:001,b:010,c:100,d:1000以此类推
那么对于一个[l,r]范围,可以使用异或来统计次数,如果异或后的数二进制位1的个数小于等于一,那么符合条件
对于操作1,快速改变一个[l,r]范围的字符,相当于对这个区间每个数进行取模的左移
用线段树维护

struct node{
    int l,r;
    int sum;
    int tag;
}tr[4*maxn];

void pushup(int p){
    tr[p].sum=tr[ls].sum^tr[rs].sum;
}

void move(int &x,int d){
    if(d%26==0)return;
    int res=0;
    for(int i=0;i<=25;i++){
        if((1ll<<i)&x){
            int t=i+(d%26);
            t%=26;
            res|=(1ll<<t);
        }
    }
    x=res;
}
void pushdown(int p){
    if(tr[p].tag){
        tr[ls].tag+=tr[p].tag;
        tr[rs].tag+=tr[p].tag;
        move(tr[ls].sum,tr[p].tag);
        move(tr[rs].sum,tr[p].tag);
        tr[p].tag=0;
    }
}
void build(int p,int l,int r,vector<int>&a){
    tr[p].l=l;tr[p].r=r;tr[p].tag=0;
    if(l==r){
        tr[p].sum=a[l];
        return;
    }
    int mid=l+r>>1;
    build(ls,l,mid,a);build(rs,mid+1,r,a);
    pushup(p);
}
void change(int p,int l,int r,int d){
    if(l<=tr[p].l&&tr[p].r<=r){
        tr[p].tag+=d;
        move(tr[p].sum,d);
        return;
    }
    pushdown(p);
    int mid=tr[p].l+tr[p].r>>1;
    if(l<=mid)change(ls,l,r,d);
    if(r>mid)change(rs,l,r,d);
    pushup(p);
}
int query(int p,int l,int r){
    if(l<=tr[p].l&&tr[p].r<=r){
        return tr[p].sum;
    }
    int res=0;
    pushdown(p);
    int mid=tr[p].l+tr[p].r>>1;
    if(l<=mid)res^=query(ls,l,r);
    if(r>mid)res^=query(rs,l,r);
    return res;
}
int count(int x){
    int res=0;
    for(int i=0;i<=25;i++){
        if(x&(1ll<<i))res++;
    }
    return res;
}
void solve(){
    int n,q;cin>>n>>q;
    string s;cin>>s;
    s=" "+s;
    vector<int>a(n+1);
    rep(i,1,n)a[i]=1ll<<(s[i]-'a');
    build(1,1,n,a);
    while(q--){
        int opt;cin>>opt;
        int l,r;cin>>l>>r;
        if(opt==1){
            int d;cin>>d;
            change(1,l,r,d%26);
        }else{
            int k=query(1,l,r);
            int num=count(k);
            if(num<=1){
                cout<<"Yes"<<endl;
            }else cout<<"No"<<endl;
        }
    }

}

D

题意:

给定一个01串,可将字符翻转,求使其只出现一个连续的1段的最小翻转次数

思路:

设区间[l,r]为1段
则[1,l-1],[r+1,n]为0段
答案为0段的1的个数+1段的0的个数
枚举r,求2xpre[i]-i的前缀最小值即可

void solve(){
    int n;cin>>n;
    string s;cin>>s;s=" "+s;
    vector<int>pre(n+1);

    rep(i,1,n){
        pre[i]=pre[i-1]+(s[i]-'0');
    }
    int ans=1e9;
    vector<int>x(n+1);
    rep(i,0,n){
        x[i]=2*pre[i]-i;
    }
    rep(i,1,n){
        x[i]=min(x[i-1],x[i]);
    }
    rep(i,1,n){
        ans=min(ans,pre[n]-(2*pre[i]-i)+x[i]);
    }
    cout<<ans<<endl;
}

E

题意:

给定一颗树,求1到n的最小按位或路径

思路:

让答案二进制位从高位到低位枚举i
看能不能令1到n的按位或路径上 没有路径的二进制 或上i 是1
取那些路径或上i不是1的,并且 排除 路径或上之前枚举的i为1且答案不包含i的 ,观察1和n是否连通
前者好理解,后者是为了保证对答案贡献小的位(1 2 4 8 16...发现2^i > (2^0+2^1+..2^(i-1)) 不能破坏之前的贪心策略
如果不连通,说明最小按位或路径一定包含i
使用并查集判断连通性

int n,m;
int f[maxn];
int find(int x){
    if(f[x]!=x){
        f[x]=find(f[x]);
    }return f[x];
}
void merge(int x,int y){
    if(find(x)!=find(y)){
        f[find(x)]=find(y);
    }
}
void solve(){
    cin>>n>>m;
    vector<pair<pii,int>>edge(m+1);
    vector<int>k(32);
    rep(i,1,m){
        int u,v,w;cin>>u>>v>>w;
        edge[i]={{u,v},w};
    }
    int ans=0;
    for(int i=32;i>=0;i--){
        rep(j,1,n){
            f[j]=j;
        }
        for(int j=1;j<=m;j++){
            auto[x,w]=edge[j];
            int u=x.fi,v=x.se;
            int ok=1;
            for(int z=32;z>i;z--){
                if((w&(1ll<<z))&&(!k[z])){
                    ok=0;
                }
            }
            if(!ok)continue;

            if(!((1ll<<i)&w)){
                merge(u,v);
            }
        }
        if(find(n)!=find(1)){
            ans|=(1ll<<i);
            k[i]=1;
        }
    }
    cout<<ans<<endl;
}

F

题意:

给定一个排列代表高度,可以任意挑选一个起点,可以跳到与现在高度差大于等于D且在位置为中心,半径为r的范围内的的点
求最多能跳多少次

思路:

记dp[i]为到达高度为i最多能跳多少次
考虑从低到高1->n转移
因为高度是排列,显然一个i是从[1,i-D]中最大的dp值+1转移得到的
且有个限制,即它是从限制范围内转移得到的
求区间dp最大值可以用线段树维护
用队列控制i-D何时在线段树中插入

int dp[maxn];
struct node{
    int l,r;
    int mx;
}tr[4*maxn];

void pushup(int p){
    tr[p].mx=max(tr[ls].mx,tr[rs].mx);
}
void build(int p,int l,int r){
    tr[p].l=l;tr[p].r=r;
    if(l==r){
        tr[p].mx=0;return;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(p);
}
void change(int p,int pos,int x){
    if(tr[p].l==tr[p].r&&tr[p].l==pos){
        tr[p].mx=x;
        return;
    }
    int mid=tr[p].l+tr[p].r>>1;
    if(pos<=mid)change(ls,pos,x);
    else change(rs,pos,x);
    pushup(p);
}
int query(int p,int l,int r){
    if(l<=tr[p].l&&tr[p].r<=r){
        return tr[p].mx;
    }
    int res=0;
    int mid=tr[p].l+tr[p].r>>1;
    if(l<=mid)res=max(res,query(ls,l,r));
    if(r>mid)res=max(res,query(rs,l,r));
    return res;
}
void solve(){
    int n,d,r;cin>>n>>d>>r;
    vector<int>a(n+1);
    vector<int>pos(n+1);
    rep(i,1,n){
        cin>>a[i];
        pos[a[i]]=i;
    }
    build(1,1,n);
    queue<int>q;
    rep(i,1,n){
        if(i-d>=1){
            int u=q.front();q.pop();
            change(1,pos[u],dp[u]);
        }
        int res=query(1,max(1ll,pos[i]-r),min(n,pos[i]+r));
        dp[i]=max(dp[i],query(1,max(1ll,pos[i]-r),min(n,pos[i]+r))+1);
        q.push(i);
    }
    int ans=0;
    rep(i,1,n){
        ans=max(ans,dp[i]);
        // debug(dp[i]);
    }
    cout<<max(0ll,ans-1)<<endl;
}

F

题意:

给定n,求n!个排列中,mid在连续区间的连乘积

思路:

考虑O(n^2)的求贡献做法
考虑将1~n中每一个数作为区间的mid,单独算出每一个数对答案的贡献
枚举排列的这个数作为mid的区间长度len
len从1~n
类似插空法的思想其他不在区间的数排列有(n-len)!种方法
由于mid定义是自动排好序的,所有区间内的数有len!种方法
把区间插入到不在区间的数排列中有(n-len+1)种方法
考虑这个数作为mid,那么区间内[1,len/2]个数是从小于它的数中取的
[len/2+2,len]是从大于它的数中取的
用预处理组合数的方式快速求解
1~n中一个数i对答案的贡献是:上面的乘积之和作为幂,i作为底
答案为 每个数贡献的乘积
由于幂可能很大,因为mod为质数,所以可以欧拉降幂,即 将幂次取模mod-1,其他的直接取模mod

int ksm(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int C[6005][6005],fact[6005];
void init(){
    fact[0]=1;
    for(int i=1;i<=6001;i++){
        fact[i]=fact[i-1]*i%mod_phi;
    }
    for(int i=0;i<=6001;i++){
        C[i][0]=C[i][i]=1;
        for(int j=1;j<=i-1;j++){
            C[i][j]=(C[i-1][j]%mod_phi+C[i-1][j-1]%mod_phi)%mod_phi;
        }
    }
}
void solve(){
    int n;cin>>n;
    init();
    int ans=1;
    for(int i=1;i<=n;i++){
        int cnt=0;
        for(int j=1;j<=n;j++){
            int x=i-1,y=n-i;
            int u=j/2,v=j-1-u;
            int res=((((C[x][u]*C[y][v]%mod_phi)*fact[j]%mod_phi)*fact[n-j]%mod_phi)*(n-j+1)%mod_phi);
            cnt=(cnt%mod_phi+res%mod_phi)%mod_phi;
        }
        ans=ans*ksm(i,cnt)%mod;
    }
    cout<<ans<<endl;
}

E

题意:

给定一给abc字符串,q次换指定字符的机会,求字典序最小的结果

思路:

a一定不换
b->a 优先级高于 b->c->a
c->a 优先级高于 c->b->a 高于 c->b
将每种操作的时间点存下来,再考虑换不换
遍历s,贪心的一定先把前面b和c能换的换了
用set二维数组+二分的方式进行操作
如果能操作,需要统计后把set存的时间点给删除掉

void solve(){
    int n,q;cin>>n>>q;
    string s;cin>>s;
    s=" "+s;
    set<int>st[3][3];
    rep(i,1,q){
        char a,b;cin>>a>>b;
        st[a-'a'][b-'a'].insert(i);
    }
    rep(i,1,n){
        if(s[i]=='a')continue;

        //b->a
        //b->c->a
        if(s[i]=='b'){
            if(st[1][0].empty()){
                if(st[1][2].empty())continue;

                //c->a
                //b->c
                auto it=st[2][0].lower_bound(*st[1][2].begin());
                if(it!=st[2][0].end()){
                    s[i]='a';
                    st[1][2].erase(st[1][2].begin());
                    st[2][0].erase(it);
                }
            }else{
                s[i]='a';
                st[1][0].erase(st[1][0].begin());
            }
        }
        //c->b->a
        if(s[i]=='c'){
            if(st[2][0].empty()){
                if(st[2][1].empty())continue;
                s[i]='b';
                auto it=st[1][0].lower_bound(*st[2][1].begin());
                if(it!=st[1][0].end()){
                    s[i]='a';
                    st[1][0].erase(it);
                }
                st[2][1].erase(st[2][1].begin());
            }else{
                s[i]='a';
                st[2][0].erase(st[2][0].begin());
            }
        }
    }
    
    rep(i,1,n){
        cout<<s[i];
    }
    cout<<endl;
}
posted @ 2025-06-07 10:39  Marinaco  阅读(24)  评论(0)    收藏  举报
//雪花飘落效果