NOIP提高模拟-20181019-T1-加密

写在前面

我还是太菜了,考试的时候只写了一个70pts的暴力,用map居然可以过70分。这么良心的出题人少见了啊,然而之后的满分做法会让你们知道出题人有多么毒瘤。

Solution

70pts做法

反正题目都给了你一个Hash函数,直接预处理然后map储存就可以搞定。

100pts做法

本题实质上是一个编码与解码的问题,我们只需要对于每个给定的输入,一波逆运算就可以AC。
然而,Hash函数长成这样:

unsigned int Hash(unsigned int t){
    unsigned int t=v;
    t=t+(t<<10);
    t=t^(t>>6);
    t=t+(t<<3);
    t=t^(t>>11);
    t=t+(t<<16);
    return t;
}

观察以后发现,诶,有个异或操作,woc,我不会还原异或!!!!!
不要慌,仔细想一想,对于t=t ^ (t>>i),我们在计算之前,可以考虑把t分成i32份(结果向上取整),为什么要这么做呢?因为可以看做是这样的:

按照上面的想法,我们将现在的数分成三份(当前的数按照二进制表示,是一个01串):X1,X2,X3
那么X1可以看作是t<<22,那么X2怎么算呢?我们可以这样想:

这个想法很直观,但是怎么在数学上实现呢?先让t>>11,这样就裁掉X3了,然后我们再让t异或上X1<<11,因为一个数异或自己等于零,异或零等于自己,所以说我们让t当中原来和X1相等的部分和X1<<11异或,这样就在裁掉X1的同时,保证了X2是完好的。X3按照一样的思想,稍加推算也可以得出式子。
那么,现在我们有X1,X2,X3了,怎么进一步还原t呢?注意到,现在的数,实际上是原数异或上X1,X2两段得到的,也就是说现在的X2是原数的X2和现在的X1异或得到的,所以说反过来异或一次,即X2异或X1,就是原数的X2那一段,X3同理,X1是没有变的。所以会所我们再将这几段拼接回去就好了。方法也很简单,将X1<<22异或X2<<11再异或X3即可,可以自己手推一下,加深理解。
这样的话,异或的逆运算就解决了,而加又怎么办呢?
其实这相当解一个方程:
(2i+1)xt(mod232)
显然扩展欧几里得算法就可以搞定这一步,具体见代码实现。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
	ll sum=0,neg=1;
	char c=getchar();
	while((c>'9'||c<'0')&&c!='-') c=getchar();
	if(c=='-') neg=-1,c=getchar();
	while(c>='0'&&c<='9') sum=(sum<<1)+(sum<<3)+c-'0',c=getchar();
	return sum*neg;
}
/* 70pts做法,十分暴力的打表。 
map<unsigned int,int> h;
unsigned int Hash(unsigned int v){
    unsigned int t=v;
    t=t+(t<<10);
    t=t^(t>>6);
    t=t+(t<<3);
    t=t^(t>>11);
    t=t+(t<<16);
    return t;
}
int main(){
    freopen("encrypt.in","r",stdin);
    freopen("encrypt.out","w",stdout);
    for(unsigned int i=1;i<=100000;i++) h[Hash(i)]=i;
    int q;
    q=read();
    for(int i=1;i<=q;i++){
        int t;
        t=read();
        printf("%d\n",h[t]);
    }
    return 0;
}
*/
//100pts
const ll mod=1ll<<32;
ll exgcd(ll a,ll b,ll &x,ll &y){
	b?(exgcd(b,a%b,y,x),y-=a/b*x):(x=1,y=0);
}
ll calcinv(ll t){
	ll x,y;
	exgcd(t,mod,x,y);
	x=(x%mod+mod)%mod;
	return x;
}
ll ksc(ll a,ll b,ll ret=0){//快速乘,直接乘会爆long long 
	for(;b;b>>=1,a=(a+a)%mod) if(b&1) ret=(ret+a)%mod;
	return ret;
}
int main(){
    /*ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);*/
    int q;
    q=read();
    for(int i=1;i<=q;i++){
        ll t=read();
        //第一步还原->还原t=t+(t<<16) 
        t=ksc(t,calcinv((1ll<<16)+1));
        //第二步还原->还原t=t^(t>>11) 
        ll x1=t>>22,x2=(t>>11)^(x1<<11),x3=t^(x1<<22)^(x2<<11);
		x2=x2^x1; x3=x3^x2; t=(x1<<22)^(x2<<11)^x3;
		//第三步还原->还原t=t+(t<<3)
        t=ksc(t,calcinv((1ll<<3)+1));
        //第四步还原->还原t=t^(t>>6)
        x1=t>>30,x2=(t>>24)^(x1<<6),x3=(t>>18)^(x1<<12)^(x2<<6);
        ll x4=(t>>12)^(x1<<18)^(x2<<12)^(x3<<6),x5=(t>>6)^(x1<<24)^(x2<<18)^(x3<<12)^(x4<<6);
        ll x6=t^(x1<<30)^(x2<<24)^(x3<<18)^(x4<<12)^(x5<<6);
        x2^=x1,x3^=x2,x4^=x3,x5^=x4,x6^=x5;
        t=(x1<<30)^(x2<<24)^(x3<<18)^(x4<<12)^(x5<<6)^x6;
        //第五步还原->还原t=t+(t<<10)
        t=ksc(t,calcinv((1ll<<10)+1));
        printf("%lld\n",t);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值