[JZOJ5449] Pacifist

题目描述

papyrus 喜欢谜题… 来解一道如何?
在你面前有一个被加密了的数组,其原数组是一个等差序列,你面前的则是将原数组中的所有数字都对m 取模再打乱后而得到的新数组
papyrus 给你出的谜题就是还原出原等差序列
因为papyrus 喜欢质数,所以他给你出的谜题中的m 一定是质数
对于10% 的数据满足2<= m <= 5
对于另10% 的数据满足n = m
对于另10% 的数据满足n = m - 1
对于100% 的数据满足2 <= m <= 10^9 +7; 2 <= n <= 10^5,m 是质数,给出的序列中有可能有相同元素。

分析

这道题方法就很多了,思维比较发散。
先讲不太稳的算法。随便选定一个数a_i,它跟另外每一个数的差,一定有一个是公差。那么我们可以枚举公差,然后再来判断是否合法。
随机超时水法的判断方式是:二分确定首项是什么,即二分看看减多少个公差回到首项。因为如果公差合法,那么ai往前连续一段肯定都出现在数组中,而且m是质数,不会有鬼畜情况。唯一的鬼畜情况是2n>=m,二分的上界太大,有可能跳到序列的末尾,这时候要调整二分上界,这里可能花费很多时间。这之后,我们随机判断等差数列每一项是否出现,实现我用的是把所有系数random shuffle一下。这样我们期望复杂度大概是 O(n2/2) ,若我们确定的数记为a1,对a也shuffle,那么期望在 an/2 的地方找到合法公差。
另一个类似哈希的特征值判断方法是利用等差数列的一、二、三次求和的公式进行判断。我们枚举公差,然后用 Sn=a1n+n(n1)/2d 来解出 a1 ,因为S的值我们可以直接用读入的数据算出,那么可以算出 a1 ,而m又是质数,解在模意义下唯一。这样求出之后,我们可以直接看看 S2,S3 的特征值符不符合,即看看用公式和直接用读入数据算出来结果一不一样。如果一样直接输出,正确率很高。

一个比较靠谱的方法是利用等差数列的性质。我们随便找一个数,再枚举序列的每一个数,我们知道 2ai=aix+ai+x ,那么如果不是 ix<1i+x>n ,一个数一定能以我们找的那个数为对称轴,有一个跟他对称的数。那么我们不断像这样把序列匹配的删除,删除完之后假设删了cnt个,我们可以确定出这个数的位置,当然有两种可能,cnt+1或者n-cnt。那么我们再在剩下的数再确定某一个数是cnt’+1或者n-cnt’。这样四种情况肯定有合法的,直接判断一下即可。然而鬼畜的2n>=m还是要考虑,这个比较简单,只要把出现的剩余系的补集找出来,对补集做一样算法,这里面肯定2n

代码

给出第一个水法代码…

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=1e5+5,mo=60000011;
int D,l,r,mid,tmp,a[N],b[N],v[mo+5],i,j,n,m,pp,k,st,kan;
int hash(int x)
{
    k=x%mo;
    while (v[k]&&v[k]!=x+1) k=(k+1)%mo;
    return k;
}
int main()
{
    freopen("t1.in","r",stdin);
//  freopen("t1.out","w",stdout);
    scanf("%d %d",&m,&n);
    if (m==n)
    {
        printf("0 1\n");
        return 0;
    }
    fo(i,1,n) scanf("%d",a+i);
    if (n==1)
    {
        printf("%d 0\n",a[1]);
        return 0;
    }
    fo(i,1,n-1) b[i]=i;
    srand(m);
    random_shuffle(b+1,b+n);
    sort(a+1,a+1+n);
    fo(i,1,n)
        v[hash(a[i])]=a[i]+1;
    fo(i,2,n)
    {
        D=a[i]-a[1];
        if (D<0) D+=m;
        if (D>m/2) D=m-D;
        if (!D) continue;
        l=0;
        st=a[1];
        while (l<n-1&&v[hash((st-D+m)%m)])
        {
            r=min(n-1,m-n+l);
            while (l<r)
            {
                mid=(l+r+1)/2;
                tmp=((a[1]-1ll*mid*D)%m+m)%m;
                if (v[hash(tmp)]) l=mid;
                else r=mid-1;
            }
            st=((a[1]-1ll*l*D)%m+m)%m;
        }
        //st=((a[1]-1ll*l*D)%m+m)%m;
        pp=1;
        fo(j,1,n-1)
        {
            kan++;
            tmp=(st+1ll*b[j]*D)%m; 
            if (!v[hash(tmp)])
            {
                pp=0;
                break;
            }
        }
        if (pp)
        {
            printf("%d %d\n",st,D);
            return 0;
        }
    }
    printf("-1\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值