Codeforces Round #670 (Div. 2) E.Deleting Numbers(交互+分块 素数分布 猜数)

题目

交互题,首先给定一个n(n<=1e5),并想好一个数x(1<=x<=n),

你需要在不超过10000次操作中,猜出这个数x,

集合S里初始有{1,...,n}共n个数,操作分三种,

①A a,询问当前集合中a的倍数有多少个

②B a(a>1),询问当前集合中a的倍数有多少个,并删掉a的倍数,特别地,x不会被删掉

③C a,猜出当前的数x的值为a,只能执行一次,程序会被结束

思路来源

codeforces群里口胡

题解

sum实时统计当前还有多少数没有被删,

预处理所有素数,9592个,考虑在这上面做文章,

 

首先,考虑<=sqrt(n)的素数,

小于317的素数也就65个,

对其每个素数p,询问一次B,删掉p的所有倍数,

再从p开始,p^2,p^3,不断往上询问A,

如果出现当前p^k非零,说明x是p^k的倍数,没有在删p的倍数的操作中被删掉

考虑到2^17>1e5,

这部分最多会被B65次,A65次,A如果命中,最多再询问17次

 

一个很重要的事实是,大于sqrt(n)的素因子只能在x里出现一次,

①如果ans>sqrt(n),说明ans不存在大于sqrt(n)的素因子,直接回答答案

②ans<sqrt(n),考虑>sqrt(n)的9527个素数,

(1)如果ans!=1,

说明x已经被小素数p删过,但是没删掉,

这样在遍历大素数q的时候,会出现p*q的存在性与预想不一致的情况,遍历即可找出

(2)如果ans==1,

说明ans就是一个大素数,考虑对其按根号分块,约98个一块

对每个块内的素数先逐个B删除,再询问一次1,

如果数量与预想不一致,说明x保留在该块内,再对该块内逐个A询问找出即可

 

由于+17和分块对应不同的两种情况,

所以最后询问次数上限,为9592+98(询问1)+98(check块内每个)+65+65

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,pr[N],c,sum;
bool vis[N],del[N];
int ask(char x,ll y){
    printf("%c %lld\n",x,y);
    fflush(stdout);
    if(x=='C')return 0;
    int v;
    scanf("%d",&v);
    return v;
}
void Del(int x){
    for(int i=x;i<=n;i+=x){
        if(!del[i]){
            sum--;
            del[i]=1;
        }
    }
}
int cal(int x){
    int tot=0;
    for(int i=x;i<=n;i+=x){
        tot+=(!del[i]);
    }
    return tot;
}
int main(){
    scanf("%d",&n);
    sum=n;
    for(int i=2;i<=n;++i){
        if(!vis[i]){
            pr[++c]=i;
        }
        for(ll j=1ll*i*i;j<=n;j+=i){
            vis[j]=1;
        }
    }
    int ans=1,pos;
    for(int i=1;i<=c && 1ll*pr[i]*pr[i]<=n;++i){
        int num=ask('B',pr[i]);
        Del(pr[i]);
        for(ll v=pr[i];v<=n;v*=pr[i]){
            int now=ask('A',v);
            if(now){
                ans*=pr[i];
            }
            else{
                break;
            }
        }
        pos=i;
    }
    int sq=sqrt(n);
    if(ans>sq){
        ask('C',ans);
    }
    else{
        if(ans!=1){
            for(int i=pos+1;i<=c;++i){
                int now=ask('A',pr[i]),tot=cal(pr[i]);
                if(now!=tot){
                    ans*=pr[i];
                    break;
                }
            }
            ask('C',ans);
        }
        else{//ans=一个大素数
            int blk=sqrt(c-pos);
            for(int l=pos+1;l<=c;l+=blk){
                int r=min(l+blk-1,c);
                for(int i=l;i<=r;++i){
                    int now=ask('B',pr[i]);
                    Del(pr[i]);
                }
                int num=ask('A',1);
                if(num!=sum){
                    for(int i=l;i<=r;++i){
                        int now=ask('A',pr[i]),tot=cal(pr[i]);
                        if(now!=tot){
                            ans*=pr[i];
                            break;
                        }
                    }
                    break;
                }
            }
            ask('C',ans);
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值