题目描述
一年一度的运动会开始了. 有N个选手参赛, 第i个选手有一个能力值Ai, 比赛一共进行了 2^M天. 在第j天(0⩽ j⩽2^M−1 )的比赛中, 第i个选手的得分为A[i] xor j, 然后从大到小排名, 排名为x(x从0开始)的同学会获得 x^2的积分, 你需要求出每个同学最后总的积分和q[i]模 1e9+7 的结果p[i].
为了避免输出文件过大, 你只要输出p[i]的异或和即可.
输入格式
第一行三个整数, 分别代表N, M.
接下来一行N个整数, 第i个数代表A[i].
输出格式
一个整数代表答案.
样例
race.in
3 2
0 1 2
race.out
8
数据范围
对于10%的数据, M <= 5.
对于30%的数据, N <= 100.
对于50%的数据, N <= 1000.
对于100%的数据, N <= 200000, M <= 30, A[i] < 2^M .
分析:
我问所有会做的人:这道题怎么做啊?
他们都说:你先做这道题:管道取珠,之后就会了
那好吧,只好先写这道题了
考虑怎么求出x的答案.
平方相当于是枚举两个人(可以相同), 把这两个人同时排在x前面的天数计入答案
那么对于x, 如果我们求出f[i]也就是能力值的二进制中第i+1到M-1位都和他相等且第i位不同的人有多少个, 那么这些人是否排在他前面只由第i位确定, 一共有2^(M-1)天,
而且不需要从这些人中枚举两个人了, 因为直接平方即可
我们只要枚举i和j, f[i]这些人和f[j]这些人同时排在他前面的天数为2^(M-2),
所以把2*f[i]*f[j]*2^(M-2)计入答案. 具体实现有很多方法, 可以用trie树实现.
在看std的时候看到这样一句:assert(0);
定义
assert宏的原型定义在
#include <assert.h>
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
//这里写std
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#define ll long long
using namespace std;
const int N=200003;
const int mod=1000000007;
int n,m,A,ch[N*30][2],tot=0,sz[N*30];
ll ans=0;
void insert(int A)
{
int now=0;
for (int i=m-1;i>=0;i--)
{
int x=(A>>i)&1;
if (!ch[now][x]) ch[now][x]=++tot;
now=ch[now][x];
sz[now]++; //每个节点上的子串个数
}
}
void dfs(int x,ll sum,ll num)
{
ll tmp,size;
size=sz[ch[x][1]],tmp=0;
tmp=size*size%mod*(1<<m-1)%mod;
tmp=(tmp+size*num%mod*(1<<m-1)%mod)%mod;
if (ch[x][0]) dfs(ch[x][0],(sum+tmp)%mod,num+size);
size=sz[ch[x][0]],tmp=0;
tmp=size*size%mod*(1<<m-1)%mod;
tmp=(tmp+size*num%mod*(1<<m-1)%mod)%mod;
if (ch[x][1]) dfs(ch[x][1],(sum+tmp)%mod,num+size);
if (!ch[x][0]&&!ch[x][1]) ans^=sum;
}
map<int,bool> vis;
int main()
{
freopen("race.in","r",stdin);
freopen("race.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&A);
insert(A);
if (vis.count(A)) return 0;
vis[A]=1;
}
dfs(0,0,0);
printf("%lld",ans);
return 0;
}