[蓝桥杯 2021 国 AB] 异或三角

题目描述

给定 TTT 个数 n1,n2,⋯ ,nTn_{1}, n_{2}, \cdots, n_{T}n1,n2,,nT, 对每个 nin_{i}ni 请求出有多少组 a,b,ca, b, ca,b,c 满足:

  1. 1≤a,b,c≤ni1 \leq a, b, c \leq n_{i}1a,b,cni;

  2. a⊕b⊕c=0a \oplus b \oplus c=0abc=0 ,其中 ⊕\oplus 表示二进制按位异或;

  3. 长度为 a,b,ca, b, ca,b,c 的三条边能组成一个三角形。

输入格式

输入的第一行包含一个整数 TTT

接下来 TTT 行每行一个整数, 分别表示 n1,n2,⋯ ,nTn_{1}, n_{2}, \cdots, n_{T}n1,n2,,nT

输出格式

输出 TTT 行, 每行包含一个整数, 表示对应的答案。

输入输出样例 #1

输入 #1

2
6
114514

输出 #1

6
11223848130

说明/提示

对于 10%10 \%10% 的评测用例, T=1,1≤ni≤200T=1,1 \leq n_{i} \leq 200T=1,1ni200; 对于 20%20 \%20% 的评测用例, T=1,1≤ni≤2000T=1,1 \leq n_{i} \leq 2000T=1,1ni2000

对于 50%50 \%50% 的评测用例, T=1,1≤ni≤220T=1,1 \leq n_{i} \leq 2^{20}T=1,1ni220;

对于 60%60 \%60% 的评测用例, 1≤T≤100000,1≤ni≤2201 \leq T \leq 100000,1 \leq n_{i} \leq 2^{20}1T100000,1ni220;

对于所有评测用例, 1≤T≤100000,1≤ni≤2301 \leq T \leq 100000,1 \leq n_{i} \leq 2^{30}1T100000,1ni230

蓝桥杯 2021 国赛 A 组 I 题(B 组 J 题)。

我的题解

现有的题解简洁又优美,状态转移方程清晰易懂。

相比之下,我的做题思路复杂,程序丑陋又效率低下。

下面简单讲讲我的思路。

计算有多少三元组 (a,b,c)(a,b,c)(a,b,c) 满足

  1. a>b>ca>b>ca>b>c ;
  2. a=b⊕ca=b \oplus ca=bc ;
  3. a<b+ca<b + ca<b+c .

然后再对结果乘以6。

不难发现,满足上列条件的三元组具有以下特征——

  1. aaa 的最高位为 lenlenlen,则 alen=1,blen=1,clen=0a_{len}=1,b_{len}=1,c_{len}=0alen=1,blen=1,clen=0
  2. 存在 pospospos,使得 apos=1,bpos=0,cpos=1a_{pos}=1,b_{pos}=0,c_{pos}=1apos=1,bpos=0,cpos=1
  3. posmax⁡=max⁡{pos}pos_{\max}=\max\{pos\}posmax=max{pos} 使得 apos=1,bpos=0,cpos=1a_{pos}=1,b_{pos}=0,c_{pos}=1apos=1,bpos=0,cpos=1
  4. 对任意 posmax⁡<i<lenpos_{\max}<i<lenposmax<i<len,有 ai=bia_i=b_iai=bi
  5. 存在 k<posmax⁡k<pos_{\max}k<posmax,使得 ak=0,bk=1,ck=1a_{k}=0,b_{k}=1,c_{k}=1ak=0,bk=1,ck=1

考虑枚举 posmax⁡pos_{\max}posmax,若对答案产生贡献,则

  1. 存在 i>posmax⁡i>pos_{\max}i>posmax,使得 ai=1a_i=1ai=1 ;若有 numnumnum 个自由空位,系数贡献为 2num−12^{num}-12num1

  2. 存在 i<posmax⁡i<pos_{\max}i<posmax,使得 ai=0a_i=0ai=0 ;若有 numnumnum 个自由空位,系数贡献为 fnum=∑i=1num(2i−1)∗2num−j∗Cnumif_{num}=\sum_{i=1}^{num} (2^i-1)*2^{num-j}*C_{num}^ifnum=i=1num(2i1)2numjCnumi ( iii 表示0的个数)

先考虑 aaa 最高位 lenlenlen 取0,从1到 len−2len-2len2 枚举 posmax⁡pos_{\max}posmax 即可。

再考虑 aaa 最高位 lenlenlen 取1的情况,从1到 len−1len-1len1 枚举 posmax⁡pos_{\max}posmax

非边界情况高位系数很好算,即截取 len−1len-1len1posmax⁡+1pos_{\max}+1posmax+1 转为十进制,低位系数为 fposmax⁡f_{pos_{\max}}fposmax

边界情况需特殊处理,来个具体数字作为例子, N=(11010101)2N=(11010101)_2N=(11010101)2

假设现在 posmax⁡=6pos_{\max}=6posmax=6,即求解 aaa 的最低6位为 000000⋯010101000000 \cdots 010101000000010101 时,存在 k<posmax⁡k<pos_{\max}k<posmax,使得 ak=0,bk=1,ck=1a_{k}=0,b_{k}=1,c_{k}=1ak=0,bk=1,ck=1 分解法数量。

从高到低对1进行处理,转化为 000000⋯001111000000 \cdots 001111000000001111010000⋯010011010000 \cdots 010011010000010011010100010100010100010101010101010101 四个部分。

再分别考虑k是否在后缀处,进行计算。

如果k不在后缀处,根据更高位的01数量计算左侧系数 (2num0−1)∗2num1(2^{num_0}-1)*2^{num_1}(2num01)2num1,后缀处01可任取,如果有 numnumnum 个后缀连续1,右侧系数为 4num4^{num}4num.

如果k在后缀处,根据更高位的1的数量计算左侧系数 2num12^{num_1}2num1;如果有 numnumnum 个后缀连续1,右侧系数为 fnumf_{num}fnum.

代码:

typedef long long ll;
#include<bits/stdc++.h>
using namespace std;
int fj[35];
ll ycl[35],yh[35][35],Ans[35];
ll work0(int pos)
{
    ll ans=0;
    int lst1=-1,zero=0,one=0;
    while (~pos)
    {
        if (fj[pos])
        {
            ans+=((1ll<<zero+1)-1)*(1ll<<one)*(1ll<<pos)*(1ll<<pos);
            ans+=(1ll<<one)*ycl[pos];
            one++;
        }
        else
            zero++;
        pos--;
    }
    ans+=((1ll<<zero)-1)*(1ll<<one);
    return ans;
}
void work(int N)
{
    int len=-1;
    while (N) fj[++len]=N%2,N/=2;
    ll ans=Ans[len];
    int now=0;
    for (int i=len-1;~i;i--)
    {
        ans+=ycl[i]*now;
        if (fj[i])
            ans+=work0(i-1);
        now=now*2+fj[i];
    }
    ans*=6;
    cout<<ans<<endl;
}
void pre()
{
    yh[0][0]=1;
    for (int i=1;i<=30;i++)
    {
        yh[i][0]=1;
        for (int j=1;j<=i;j++)
            yh[i][j]=yh[i-1][j-1]+yh[i-1][j];
    }
    for (int i=1;i<=30;i++)
    {
        for (int j=1;j<=i;j++)
            ycl[i]+=((1ll<<j)-1)*(1ll<<(i-j))*yh[i][j];
    }
    ll ans;
    for (int len=0;len<=30;len++)
    {
        ans=0;
        for (int i=0;i<len;i++)
        {
            int j=len-1-i;
            ans+=ycl[i]*((1ll<<j)-1);
        }
        Ans[len]=ans;
    }
}
int main()
{
    pre();
    int T;cin>>T;
    for (int i=1;i<=T;i++)
    {
        int N;cin>>N;
        work(N);
    }
    return 0;
}

时间复杂度
O(T(log⁡N)2)\mathcal{O}(T(\log N)^2)O(T(logN)2)

我的方法实在是太复杂了,时间复杂度也不优秀。

还是希望大家多学学那些优美的数位dp。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值