写在前面
我还是太菜了,考试的时候只写了一个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)x≡t(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;
}