CF1114E Arithmetic Progression(交互题,二分,随机算法)

既然是在CF上AC的第一道交互题,而且正是这场比赛让我升紫了,所以十分值得纪念。

题目链接:CF原网

题目大意:交互题。

有一个长度为 $n$ 的序列 $a$,保证它从小到大排序后是个等差数列。你不知道这个序列长什么样,但你可以询问:

  • $?\ i$ 表示询问 $a_i$ 的值;
  • $>\ x$ 表示询问序列中是否存在严格大于 $x$ 的数。

你可以问不超过 $60$ 个询问。

现在,你需要求出这个等差数列的首项(也就是 $a$ 中的最小值)和这个等差数列的公差(也就是等差数列中相邻两个数的差)。

$2\le n\le 10^6,0\le a_i\le 10^9$。


 看,第二个操作,明显暗示我们要二分最大值。(除了这个也没啥用了吧?)

用掉 $\log 10^9=30$ 个操作。

令 $mx$ 为我们二分出的最大值。

设公差为 $d$,那么 $\forall 1\le i\le n,d|(mx-a_i)$。

也就是如果我们知道的 $a_i$ 有 $x_1,x_2,x_3\dots,x_k$,那么 $d|\gcd(mx-x_1,mx-x_2,mx-x_3\dots,mx-x_k)$。

但这样就有一个严峻的问题:如果 $k$ 不够大,直接 $\gcd$ 得到的可能不是答案!

无路可走了,只有一个想法:随机!

我们随机 $30$ 个 $i$,询问这些 $a_i$ 的值。(为什么要随机呢?因为如果直接取前 $30$ 个 $a_i$ 很可能会被毒瘤卡掉)

$d$ 就是所有 $mx-a_i$ 的 $\gcd$ 了。

???这样不会错吗?

分析一下,我们设 $a_1=mx-k_1d,a_2=mx-k_2d\dots a_n=mx-k_nd$。那么我们求出的 $\gcd$ 就是随机出的 $30$ 个 $k_i$ 的 $\gcd$。

在 $10^6$ 中随机选 $30$ 个数,$\gcd$ 不为 $1$ 的概率就是出错的概率。

大致估算一下:

选出的 $k$ 都是 $2$ 的倍数的概率是 $\frac{1}{2^{30}}$。

选出的 $k$ 都是 $3$ 的倍数的概率是 $\frac{1}{3^{30}}$。

等等等等……

这样估算的话,出错概率是不会超过 $10^{-8}$ 的。

其实有更准确的求法,就是莫比乌斯反演,但由于我们只是来证明正确性的,就不讲了。

直接引用官方题解:

By some maths, we can find out the probability of our solution to fail being relatively small — approximately $1.86185\times 10^{-9}$.

直接上rand()就做完了……

了吗?

对了,这就是出题人恶毒所在了……

rand()和random_shuffle()的上界都是32767,访问不到后面大部分的元素。

于是出题人就把恶毒的数放在了前面……

那就用自己的rand()啊!写过treap吗?把那个rand搬过来就能用了!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=1000100;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline int read(){
    char ch=getchar();int x=0,f=0;
    while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
    while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
    return f?-x:x;
}
int n,cnt,pt[maxn];
inline int rnd(){    //自己的rand
    static int seed=2333;
    return seed=(((seed*666666ll+20050818)%998244353)^1000000007)%1004535809;
}
int gcd(int x,int y){
    return y?gcd(y,x%y):x;
}
int main(){
    n=read();
    if(n<=60){    //n<=60,随便玩
        int mx=0,mn=INT_MAX;
        FOR(i,1,n){
            printf("? %d\n",i);
            fflush(stdout);
            int x=read();
            mx=max(mx,x);
            mn=min(mn,x);
        }
        printf("! %d %d\n",mn,(mx-mn)/(n-1));
        fflush(stdout);
        return 0;
    }
    int l=0,r=1e9;
    while(l<r){    //二分最大值
        int mid=(l+r)>>1;
        printf("> %d\n",mid);
        cnt++;
        fflush(stdout);
        int ver=read();
        if(ver) l=mid+1;
        else r=mid;
    }
    int mx=r,ans=0;
    FOR(i,1,n) pt[i]=i;
    FOR(i,1,n) swap(pt[i],pt[rnd()%n+1]);
    FOR(i,1,min(n,60-cnt)){    //随机60-cnt个元素
        printf("? %d\n",pt[i]);
        fflush(stdout);
        int x=read();
        ans=gcd(ans,mx-x);    //取gcd
    }
    printf("! %d %d\n",mx-(n-1)*ans,ans);    //首项=末项-(项数-1)*公差
    fflush(stdout);
}
View Code

 

转载于:https://www.cnblogs.com/1000Suns/p/10362096.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值