Loading

Educational Codeforces Round 187 解题报告

前情提要

刚调好了博客主题,感觉这个挺好看的。
省流:如果离散化不麻烦,那就不要想着去动态开点。

Screenshot 2026-02-26 222811

CF2203A

不说了。

CF2203B

额不难发现 \(s=F(s)\) 满足当且仅当 \(1 \le s \le 9\),直接把 \(x\) 每一位数字可以减少的大小全部塞到优先队列里(首位注意不要删成 \(0\),如果当前位已经是最小,就不要塞到队列里,否则浪费一次操作)。直接选最大的即可。

//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t; cin>>t;
    while(t--){
        string s; cin>>s;
        bool f=false; int sum=0;
        priority_queue<int> q;
        for(char ch:s){
            if(!f){
                f=true;
                if(ch-'1'>0) q.push(ch-'1');
            }
            else{
                if(ch-'0'>0) q.push(ch-'0');
            }
            sum+=ch-'0';
        }
        int cnt=0;
        while(q.size()&&sum>=10){
            sum-=q.top(),q.pop(),cnt++;
        } cout<<cnt<<"\n";
    }
    return 0;
}

CF2203C

一开始读错题卡了二十分钟何意味?
其实第二个条件就代表二进制下数组里每一个数的二进制位为 \(1\) 的集合必须属于 \(m\) 二进制位为 \(1\) 的集合,那么考虑到低位权值总能整除高位,所以把数尽量安排在高位就行。可以直接二分答案钦定一个长度限制 \(len\),然后直接贪心,如果最后不够塞了那么 \(len\) 就是不合法的。好像要开 int128。

//to kill a living book
#include<bits/stdc++.h>
#define int long long
#define ll __int128
using namespace std;
int s,m;
bool chk(ll len){
    ll sum=s;
    for(ll i=63;i>=0;i--){
        if(m&(1ll<<i)){
            ll us=min(len,sum/(1ll<<i));
            sum-=us*(1ll<<i);
        }
    }
    return (sum==0);
}
void solve(){
    cin>>s>>m;
    ll l=0,r=1e18+1;
    while(l+1<r){
        ll mid=(l+r)/2;
        if(chk(mid)) r=mid;
        else l=mid;
    }
    if(!chk(r)) return cout<<"-1\n",void();
    int res=r; cout<<res<<"\n";
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t; cin>>t;
    while(t--) solve();
    return 0;
}

CF2203D

看到一个笑话,Alice 和 Bob 的博弈其实是 BABA 博弈(好冷)。
考虑把 \(B\) 中的所有数分为三类:

  • 能被 \(A\) 中所有数整除。
  • 能被 \(A\) 中部分数整除。
  • 不能被 \(A\) 中任何数整除。

\(vis_i\) 代表 \(i\) 可以被 \(A\) 中多少个数整除。这个直接调和级数预处理就行了,然后就可以 \(\mathcal{O}(1)\) 归类了。贪心很明显了,Alice 和 Bob 都要优先拿第二种数。看代码就行。

//to kill a living book
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,m,a[N],b[N],cc[4],vis[N<<1];
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n+m;i++) vis[i]=0;
    for(int i=1;i<=n;i++){
        cin>>a[i],vis[a[i]]++;
    }
    for(int i=n+m;i>=1;i--){
        for(int j=i+i;j<=n+m;j+=i) vis[j]+=vis[i];
    }
    cc[0]=cc[1]=cc[2]=0;
    for(int i=1;i<=m;i++){
        cin>>b[i];
        if(vis[b[i]]==n) cc[2]++;
        else if(!vis[b[i]]) cc[0]++;
        else cc[1]++;
    }
    int nxt=0;
    if(cc[1]&1) nxt=1-nxt;
    while(1){
        if(nxt==0&&cc[2]==0) return cout<<"Bob\n",void();
        if(nxt==1&&cc[0]==0) return cout<<"Alice\n",void();
        if(nxt==0) cc[2]--;
        else cc[0]--;
        nxt=1-nxt;
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t; cin>>t;
    while(t--) solve();
    return 0;
}

下面放上我被 hack 的代码,真神秘。

//to kill a living book
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+7;
int n,m,a[N],b[N],cc[4],vis[N<<1],ok[N<<1];
void solve(){
    cin>>n>>m;
    int L=1;
    for(int i=1;i<=n+m;i++) vis[i]=ok[i]=0;
    for(int i=1;i<=n;i++){
        cin>>a[i],L=lcm(L,a[i]);
        vis[a[i]]=1;
    }
    for(int i=1;i<=n+m;i++){
        if(vis[i]){
            for(int j=i;j<=n+m;j+=i) ok[j]=1;
        }
    }
    cc[0]=cc[1]=cc[2]=0;
    for(int i=1;i<=m;i++){
        cin>>b[i];
        if(b[i]%L==0) cc[2]++;
        else if(!ok[b[i]]) cc[0]++;
        else cc[1]++;
    }
    int nxt=0;
    if(cc[1]&1) nxt=1-nxt;
    while(1){
        if(nxt==0&&cc[2]==0) return cout<<"Bob\n",void();
        if(nxt==1&&cc[0]==0) return cout<<"Alice\n",void();
        if(nxt==0) cc[2]--;
        else cc[0]--;
        nxt=1-nxt;
    }
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t; cin>>t;
    while(t--) solve();
    return 0;
}

CF2203E

又是 BABA 博弈。
每一轮是独立的,单独考虑每一轮。
\(a[1..i]\) 排序后,推下式子,不难发现假设 Alice 选了 \(a\),那么 Bob 的最优决策肯定是 \(a\) 的前驱或后继中期望得分最大的那个,我们把这个值叫 \(f\)。而 Alice 的决策带来的得分 \(f\) 是关于下标呈严格山谷状的(可能山谷有平地)。而不难发现,选择 \(i\) 的前驱和后继带来的贡献分别随 \(i\) 的增加单调递增和递减。要找的那个最小值,可以直接三分,但是更聪明的方法是直接找前驱后继得分大小交界的位置。我们二分找到最后一个前驱得分小于后继得分的位置 \(pos\),然后枚举 \(pos\)\(pos+1\),取 \(f\) 较小的那个就行。
计算得分的具体方式,我们举后继的例子,其实就是一段后缀和减去后缀的长度与 Bob 选择的值的乘积,这直接拿个权值线段树维护一下就可以了。但是我因为偷懒使用动态开点卡了一万年常没有过,加上 D 的奶龙错误被 hack,惨遭掉分。

//to kill a living book
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7,mod=998244353;
int n,a[N],rt=1,tot,num[N];
int inv(int x){
    int res=1;
    for(int i=mod-2;i;i>>=1){
        if(i&1) (res*=x)%=mod;
        (x*=x)%=mod;
    } return res;
}
struct sgt{
    int s[N<<2],c[N<<2];
    #define mid (l+r>>1)
    #define ls (x<<1)
    #define rs (x<<1|1)
    void pu(int x){
        c[x]=c[ls]+c[rs];
        s[x]=s[ls]+s[rs];
    }
    void add(int x,int l,int r,int q){
        if(l==r) return s[x]+=num[l],c[x]++,void();
        if(q<=mid) add(ls,l,mid,q);
        else add(rs,mid+1,r,q);
        pu(x);
    }
    int cnt(int x,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return c[x];
        int res=0;
        if(ql<=mid) res+=cnt(ls,l,mid,ql,qr);
        if(mid<qr) res+=cnt(rs,mid+1,r,ql,qr);
        return res;
    }
    int sum(int x,int l,int r,int ql,int qr){
        if(ql<=l&&r<=qr) return s[x];
        int res=0;
        if(ql<=mid) res+=sum(ls,l,mid,ql,qr);
        if(mid<qr) res+=sum(rs,mid+1,r,ql,qr);
        return res;
    }
    int kth(int x,int l,int r,int k){
        if(l==r) return l;
        if(c[ls]>=k) return kth(ls,l,mid,k);
        else return kth(rs,mid+1,r,k-c[ls]);
    }
} Misaka;
int cl(int val,int i){
    int res=0,cc,ss;
    if(val!=1){
        int pre=Misaka.kth(1,0,tot,val-1);
        cc=Misaka.cnt(1,0,tot,1,pre-1);
        ss=Misaka.sum(1,0,tot,1,pre-1);
        res=max(res,num[pre]*cc-ss);
    }
    return res;
}
int cr(int val,int i){
    int res=0,cc,ss;
    if(val!=i){
        int nxt=Misaka.kth(1,0,tot,val+1);
        cc=Misaka.cnt(1,0,tot,nxt+1,tot);
        ss=Misaka.sum(1,0,tot,nxt+1,tot);
        res=max(res,ss-num[nxt]*cc);
    }
    return res;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    set<int> s; map<int,int> mp;
    for(int i=1;i<=n;i++) cin>>a[i],s.insert(a[i]);
    for(int x:s) mp[x]=++tot,num[tot]=x;
    for(int i=1;i<=n;i++) a[i]=mp[a[i]];
    for(int i=1;i<=n;i++){
        Misaka.add(rt,0,tot,a[i]);
        if(i<3) continue;
        int l=0,r=i+1;
        while(l+1<r){
            int m=(l+r)>>1;
            if(cl(m,i)<=cr(m,i)) l=m;
            else r=m;
        }
        int fl=1e18;
        if(l>=1) fl=min(fl,max(cl(l,i),cr(l,i)));
        if(l+1<=i) fl=min(fl,max(cl(l+1,i),cr(l+1,i)));
        cout<<fl%mod*inv(i-2)%mod<<"\n";
    }
    return 0;
}

CF2203F

不想写。
注意到蒋老师使用了 52min 通过这题。

posted @ 2026-02-26 23:02  GE9x  阅读(90)  评论(3)    收藏  举报