有\(n\)我的,每一個人有一個能力值\(a_i\)(\(a\)互不相同),有\(2^m\)場比賽\([0,2^m)\),每場比賽一我的的得分爲\(a_i \oplus j\),按照得分排序(得分每輪比賽清零),每一個人得到排名\(^2\)的積分(注意排名從0開始算),求每一個人的積分\(q\% (10^9+7)\)後的異或和,\((n\leq 200000,m\leq 30)\)c++
考場上的解法,並不是最優解法qwqui
因爲有取模操做,只能一個我的的處理,且能夠看出是要一位位處理問題的spa
假設當前處理到第i我的,設\(k_j\)爲能力值在二進制下更高位與i同樣,第j位不一樣的人數,(由這個定義可知\(\sum{k_j}=n-1\),即除i外的每一個人會且僅會被統計一次)code
暫時無論\(k\)怎麼求,能夠發現兩個數之間的大小關係僅由從高向低第一個不一樣的位(即上面\(k\)的定義),對於一個\(j\),若是第\(j\)位和第\(i\)我的同樣,那麼他就比不過那\(k_j\)我的,即排名\(+k_j\),不然排名不會增長;而第\(j\)位是0或1的機率相等,因此第\(i\)我的取或不取\(k_j\)的機率相同,那麼第\(i\)我的的積分爲\(0 + k_1^2 + k_2^2 +....+k_m^2 + (k_1+k_2)^2 + (k_1+k_2)^2 + .......(k_1+k_2+...+k_m)^2\)(就像搜索m個數選或不選同樣)排序
好像寫複雜度了?無論無論get
這個式子能夠化簡,而我比較懶(cai),並無化簡完...it
\[q=\sum_{i=1}^{m} { k_i (2^{m-1}k_i + \frac{\sum_{j=1}^{m-1}{C_{m-1}^j \times j}}{m-i} \times(n-1-k_i)) }\]編譯
預處理一些東西以後這個式子能夠\(O(m)\)求class
扯了那麼多,到底怎麼求\(k\)啊搜索
其實求\(k\)很簡單,個人方法是對\(a_i\)排個序以後從高位到低位模擬便可,由於須要二分因此圖方便用了個\(lb\),具體見代碼
時間複雜度\(O(nlog^2n)\),複雜度瓶頸爲求\(k\),理論上能夠過,實際上我自測的時候忘記關編譯選項裏面的\(O2\)了,結果跑了個0.6s還覺得隨便過。。。
成功被卡常到2.9s(STL常數好大qwq),把\(lb\)換成手寫二分便可0.5s過此題,和std速度差很少???
正解是\(O(nlogn)\)的trie,懶得打,看起來又是套路2333
#include<bits/stdc++.h> #define N 200005 #define re register #define Min(x,y) ((x)<(y)?(x):(y)) #define Max(x,y) ((x)>(y)?(x):(y)) using namespace std; typedef long long ll; const ll mod = 1000000007; int n,m; ll a[N]; ll k[35],ans=0; ll C[35][35],sum,po; template <class T> void read(T &x) { char c; int sign=1; while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48; while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign; } ll quickpow(ll a,ll b) { ll ret=1; while(b) { if(b&1) ret=ret*a%mod; a=a*a%mod; b>>=1; } return ret; } void init()//預處理一堆亂七八糟的東西 { C[0][0]=C[1][0]=C[1][1]=1; for(int i=2;i<=30;++i) { C[i][0]=1; for(int j=1;j<=i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod; } for(int i=1;i<m;++i) sum=(sum+C[m-1][i]*i%mod)%mod; sum=sum*quickpow(m-1,mod-2)%mod; po=quickpow(2,m-1); } int find(int x) { re int l=1,r=n,ret=n+1; while(l<=r) { int mid=(l+r)>>1; if(a[mid]>=x) ret=mid,r=mid-1; else l=mid+1; } return ret; } void getk(int x)//求與x的每一位不一樣的數個數:要求sigma(ki)=n-1 { ll now=0;//前面相等的位都 >=now for(re int i=m-1;i>=0;--i)//求ki { if(x>>i&1)//這一位不一樣:[ now,now+(1<<i) ) { int r=find(now+(1<<i))-1;//右邊第一個 int l=find(now);//左邊第一個 k[i]=(r-l+1); now+=(1<<i); } else//這一位不一樣:[ now+(1<<i),now+(1<<(i+1)) ) { int r=find(now+(1<<(i+1)))-1; int l=find(now+(1<<i)); k[i]=(r-l+1); } } } int main() { freopen("race.in","r",stdin); freopen("race.out","w",stdout); read(n);read(m); for(int i=1;i<=n;++i) read(a[i]); sort(a+1,a+n+1); init(); for(re int i=1;i<=n;++i) { getk(a[i]); ll pts=0; for(re int j=0;j<m;++j) pts=(pts+k[j]*((po*k[j]%mod + sum*(n-1-k[j])%mod)%mod)%mod)%mod; pts=(pts%mod+mod)%mod; ans^=pts; } cout<<ans<<endl; return 0; }