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

写在前面

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

Solution

70pts70pts做法

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

100pts100pts做法

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

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=tt=t ^ (t&gt;&gt;i)(t&gt;&gt;i),我们在计算之前,可以考虑把tt分成32i32 \over i份(结果向上取整),为什么要这么做呢?因为可以看做是这样的:

按照上面的想法,我们将现在的数分成三份(当前的数按照二进制表示,是一个0101串):X1X1,X2X2,X3X3
那么X1X1可以看作是t&lt;&lt;22t&lt;&lt;22,那么X2X2怎么算呢?我们可以这样想:

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

#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;
}
posted @ 2018-10-19 17:24  Neonen  阅读(198)  评论(0编辑  收藏  举报