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

写在前面

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

Solution

70 p t s 70pts 70pts做法

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

100 p t s 100pts 100pts做法

本题实质上是一个编码与解码的问题,我们只需要对于每个给定的输入,一波逆运算就可以AC。
然而, H a s h Hash 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=t t=t ^ ( t &gt; &gt; i ) (t&gt;&gt;i) (t>>i),我们在计算之前,可以考虑把 t t t分成 32 i 32 \over i i32份(结果向上取整),为什么要这么做呢?因为可以看做是这样的:

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

这个想法很直观,但是怎么在数学上实现呢?先让 t &gt; &gt; 11 t&gt;&gt;11 t>>11,这样就裁掉 X 3 X3 X3了,然后我们再让 t t t异或上 X 1 &lt; &lt; 11 X1&lt;&lt;11 X1<<11,因为一个数异或自己等于零,异或零等于自己,所以说我们让 t t t当中原来和 X 1 X1 X1相等的部分和 X 1 &lt; &lt; 11 X1&lt;&lt;11 X1<<11异或,这样就在裁掉 X 1 X1 X1的同时,保证了 X 2 X2 X2是完好的。 X 3 X3 X3按照一样的思想,稍加推算也可以得出式子。
那么,现在我们有 X 1 X1 X1, X 2 X2 X2, X 3 X3 X3了,怎么进一步还原 t t t呢?注意到,现在的数,实际上是原数异或上 X 1 , X 2 X1,X2 X1,X2两段得到的,也就是说现在的 X 2 X2 X2是原数的 X 2 X2 X2和现在的 X 1 X1 X1异或得到的,所以说反过来异或一次,即 X 2 X2 X2异或 X 1 X1 X1,就是原数的 X 2 X2 X2那一段, X 3 X3 X3同理, X 1 X1 X1是没有变的。所以会所我们再将这几段拼接回去就好了。方法也很简单,将 X 1 &lt; &lt; 22 X1&lt;&lt;22 X1<<22异或 X 2 &lt; &lt; 11 X2&lt;&lt;11 X2<<11再异或 X 3 X3 X3即可,可以自己手推一下,加深理解。
这样的话,异或的逆运算就解决了,而加又怎么办呢?
其实这相当解一个方程:
( 2 i + 1 ) x ≡ t ( m o d 2 32 ) \left(2^i+1\right)x\equiv t \pmod {2^{32}} (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、付费专栏及课程。

余额充值