【NOIP2017提高A组冲刺11.4】Pacifist

Description:

在你面前有一个被加密了的数组,其原数组是一个等差序列,你面前的则是将原数组中的所有数字都对m 取模再打乱后而得到的新数组
papyrus 给你出的谜题就是还原出原等差序列
保证数据有解,而且因为papyrus 喜欢质数,所以他给你出的谜题中的m 一定是质数
2 <= m <= 10^9 +7; 2 <= n <= 10^5,m 是质数,数组a 中所有数字两两不同

题解:

考场时瞎搞了个随机化,结果差点过了,事实证明这是对的,只是没有特判一种情况。

目标在于求出公差,求出来了一切都好办。

考虑对于原等差数列,我选取一个数x,则会呈现这样的情况:
x - kb, …, x- b, x, x+b, …,x + kb, x + (k + 1)b,…
或者是:
…,x - (k + 1)b, x - kb, …, x - b, x, x +b,…,x+kb

发现x两边的数是关于它对称的。

于是可以推出一个结论,如果有a+b = 2x, 则a和b在原序列中的位置关于x对称,前提是n != mo。

于是可以随机一个x,如果a+b=2x,那么a,b这些数和x是原序列中连续的一段,剩余的也是原序列中连续的一段。

可以选取小的一段进行递归,直到剩两个数的时候就可以求出公差了。

一定要特判n = mo,不然会死循环。

时间复杂度可以认为是O(n log n)的。

出题人的做法是枚举首项,可以根据等差数列一次和公式推出公差,再代入二、三次和公式即可判断这个首项是否合法。

Code:

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define fo(i, x, y) for(ll i = x; i <= y; i ++)
using namespace std;

const int N = 2e5 + 5;
const ll ni_2 = 5e8 + 4;

ll mo, n, a[N], b[N];

ll rand(int x, int y) {
    ll s = 0; fo(i, 1, 5) s = (s + rand()) % (y - x + 1);
    return s + x;
}

int d1[N], d2[N];

ll P(ll i, ll j) {
    if(j == i + 1) {
        int k = (a[j] - a[i] + mo) % mo;
        return k > mo / 2 ? mo - k : k;
    }
    ll m = rand(i, j), r = j;
    ll t1 = 0, t2 = 0;
    fo(k, i, j) {
        int ans = 0; ll x = (2 * a[m] - a[k] + mo) % mo;
        for(int l = i, r = j; l <= r; ) {
            int m = l + r >> 1;
            if(a[m] == x) {
                ans = m; break;
            }
            if(a[m] < x) l = m + 1; else r = m - 1;
        }
        if((ans && ans != k) || k == m) d1[++ t1] = a[k]; else d2[++ t2] = a[k];
    }
    fo(k, i, i + t1 - 1) a[k] = d1[k - i + 1];
    fo(k, i + t1, j) a[k] = d2[k - (i + t1) + 1];
    if((t1 < t2 && t1 != 1) || t2 == 0 || t2 == 1) return P(i, i + t1 - 1); else return P(i + t1, j);
}

int main() {
    freopen("pacifist.in", "r", stdin);
    freopen("pacifist.out", "w", stdout);
    srand(51651131);
    scanf("%lld %lld", &mo, &n);
    fo(i, 1, n) scanf("%lld", &a[i]);
    if(n == mo) {
        printf("0 1\n");
        return 0;
    }
    sort(a + 1, a + n + 1);
    ll g = P(1, n);
    sort(a + 1, a + n + 1);
    int r = 1;
    fo(i, 1, n) {
        int x = (a[i] - g + mo) % mo;
        int ans = 0;
        for(int l = 1, r = n; l <= r; ) {
            int m = l + r >> 1;
            if(a[m] == x) {
                ans = 1;
                break;
            }
            if(a[m] < x) l = m + 1; else r = m - 1;
        }
        if(!ans) {
            printf("%lld %lld\n", a[i], g);
            return 0;
        }
    }
    printf("%lld %lld\n", a[1], g);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值