[JZOJ5569]【NOI2018模拟3.8】鏼尔德

题目描述

鏼尔德是六兄弟中的老三,他喜欢老四弗斯,他希望弗斯高兴。
鏼尔德给弗斯发了一封信,为了防止别人知道,他将其加密了,请你帮助弗斯解密。
鏼尔德选择了n个正整数a1,a2…an,保证ai>a1+a2+…ai-1。令q=2^64,保证q>a1+a2+…+an。他用一个和q互质正整数r构造出bi=(ai*r) mod q。他想给弗斯发的消息是一个长度为n的二进制串c,ci=0或1。他将b1,b2,…,bn,(b1c1+b2c2+…bncn) mod q告诉了弗斯,请你帮弗斯解密出c。
子任务一:40分,保证n≤20。
子任务二:60分,保证n<64。

解题思路

很容易想到大约40以下的都可以折半搜索。问题是更大的怎么弄。
似乎除了解出a[]和r别无选择。
考虑一个n>40,可以发现 a[1]<=264n a [ 1 ] <= 2 64 − n ,尝试枚举a[1]取值,然后再考虑r的取值,假如情况不多,肯定能试到正确的取值,这之后求r的逆元,就能把a[]还原。注意到a的性质,我们可以从后往前考虑c[]的取值,贪心地拼出(b1c1+b2c2+…bncn) mod q.
那么考虑具体怎么做。
b[1]=a[1]ri264 b [ 1 ] = a [ 1 ] ∗ r − i ∗ 2 64 取极大的k
b[1]2k=a[1]2kri264k b [ 1 ] 2 k = a [ 1 ] 2 k ∗ r − i ∗ 2 64 − k ,也就是模 264k 2 64 − k 意义下。
那么在此意义下对 a[1]2k a [ 1 ] 2 k 取逆元,然后乘到左边,就可以得出r在此意义下的值。
但我们要的是2^64意义下的,可以暴力枚举更高位,然后求逆元,然后拿去check。由于数据合法,肯定能枚举出正确的东西。
后面的复杂度比较迷,不过令44为阈值会很好

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
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;
int n,i,j,st[10000000+5],mid,pd[10000000+5];
ull v,b[N],tmp,ha[10000000+5],mo=1e7,sum,x,lim,a[N],rev,rb,tv,pw,pp,kan,ra,r;
char s1[N];
int hash(ull x)
{
    int k=x%mo;
    while (pd[k]&&ha[k]!=x) 
        k=(k+1)%mo;
    return k;
}
void dfs(ull x,ull y,ull z,int lim,int s)
{
    if (x==lim) 
    {
        int t;
        if (s==1)
        {
            t=hash(z);
            ha[t]=z;
            st[t]=y;
            pd[t]=1;
        }
        else
        {
            t=hash(v-z);
            if (pd[t])
            {
                y|=st[t];
                t=hash(v-z);
                ull i,tmp;
                fo(i,1,n)
                    if (y&((ull)1<<i-(ull)1)) s1[i]='1',tmp+=b[i]*(i<=mid);else s1[i]='0';
                printf("%s\n",s1+1);
                exit(0);
            }
        }
        return ;
    }
    dfs(x+s,y,z,lim,s);
    dfs(x+s,y|((ull)1<<(ull)x-1),z+b[x],lim,s);
}
int check(ull rev)
{
    sum=a[1];
    fo(j,2,n) 
    {
        ull tmp=b[j]*rev;
        a[j]=tmp;
        if (a[j]<=sum||sum+a[j]<a[j]||sum+a[j]<sum) 
        {
            return 0;
        }
        sum+=a[j];
    }
    tv=v*rev;
    fd(j,n,1)
    {
        if (tv>=a[j]) tv-=a[j],s1[j]='1';else
        s1[j]='0';
    }
    return (tv==0);
}
ull ksm(ull x,ull y)
{
    ull ret=1;
    while (y)
    {
        if (y&1) ret=ret*x;
        y>>=1;
        x=x*x; 
    }
    return ret;
}
ull Rev(ull x,ull &mo,ull &rb)
{
    pw=64;
    while (x%2==0)
    {
        pw--;
        x/=2;
    }
    mo=(ull)1<<pw;
    if (pw==64) mo=0;
    rb=ksm(x,((ull)1<<(pw-1))-(ull)1);
}
int main()
{
    freopen("t3.in","r",stdin);
//  freopen("t3.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n)
        scanf("%llu\n",b+i);
    scanf("%llu",&v);
    if (n<=44)
    {
        mid=n/2;
        dfs(1,0,0,mid+1,1);
        dfs(n,0,0,mid,-1);
    }else
    {
        lim=1<<(64-n);
        fo(i,1,lim)
        {
            a[1]=i;
            Rev(a[1],mo,ra);
            r=ra*(b[1]>>(ull)(64-pw));
            if (mo) r%=mo;
            do
            {
                rev=ksm(r,((ull)1<<(ull)63)-(ull)1);
                if (check(rev))
                {
                    printf("%s\n",s1+1);
                }
                if (!mo) break;
                r+=mo;
            }while (r>mo);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值