11.1 T3.race(Trie+x^2的转化)

题目描述
一年一度的运动会开始了. 有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; 
}
题目描述 给你两个正整数$n$和$m$,你需要把$n$分裂成$m$个数相加的形式$n=a_1+a_2+a_3+...+a_m$,使$a_1$ or $a_2$ or $a_3$ or $...$ or $a_m$的最小,其中or位或运算。 输入 第一行一个正整数$T(1 \leq T \leq 100)$。 接下来的$T$行,每行两个正整数$n$和$m$,其中$0 \leq n < 10^{19}$,$1 \leq m \leq 10^{19}$。 输出 对于每组$n$和$m$,输出一个整数,表示其按位$or$的最小。 样例输入 5 3 1 3 2 3 3 10000 5 1244 10 样例输出 3 3 1 2000 125 算法1 Trie (异或前缀和+Trie) 设$f(d,i,x)$表示当前匹配到第$i$位,目前状态为$x$(01串),离散化后前缀异或和与$d$的异或和最小的。 算法流程: - 将$n$化为$62$进制的数,然后将高位不为$1$的位置为$1$,如$778$表示为$1000000010$ - 构造$Trie$,对于每个数,二进制位按位插入。 - 二进制位按位,从高到低枚举,当前位处于$Trie$的位置为$x$,记录$XOR$当前节点位置的异或前缀和$S$,再记录当前位的$01$串$d\in \{0,1\}$。 - 如果$d=1$,即当前为$1$位。对于该位为$0$的情况,往下搜索一层,当前异或前缀和取$x$的异或前缀和。对于该位为$1$的情况,考虑当前这个$1$的位置能否改为$0$。 - 如果$x\text{ XOR }2^i>n\text{ XOR }2^i$,则它变为$0$之后它的异或前缀和更小,于是尝试让其为$0$,此时向下搜索一层,当前状态位置取相同。 - 否则不能更改,则此位必须填$1$。 - 如果$d=0$,即当前为$0$位。对于该位为$1$的情况,当前异或前缀和取$x$的异或前缀和加上$2^i$,直接向下搜索一层。对于该位为$0$的情况,也是直接向下搜索一层,当前状态位置取相同。 思考怎么证明这个算法是对的辣~ 参考文献11fa456d6a68a666c819b6ebdcca99ecd2c57b3e 时间复杂度:$O(n+k^2)$,其中$k$是$n$的$62$进制长度。 C++ 代码 算法2 分块+DP 放弃阅读,ing 时间复杂度 参考文献 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值