[CSP校内集训]race(位运算?)

题意

\(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

Code

#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;
}
相关文章
相关标签/搜索