2023牛客暑期多校第五场 I.The Yakumo Family

题目大意

给定一段长度为 n ( 3 ≤ n ≤ 2 ∗ 1 0 5 ) n(3 \leq n \leq 2*10^5) n3n2105 的数组 a   ( 0 ≤ a i ≤ 1 0 9 ) a\ (0\leq a_i \leq 10^9) a (0ai109),求
∑ 1 ≤ l 1 ≤ r 1 ≤ l 2 ≤ r 2 ≤ l 3 ≤ r 3 X O R ( l 1 , r 1 ) ∗ X O R ( l 2 , r 2 ) ∗ X O R ( l 3 , r 3 ) \sum_{1\leq l_1\leq r_1\leq l_2\leq r_2\leq l_3\leq r_3}XOR(l_1,r_1)*XOR(l_2,r_2)*XOR(l_3,r_3) 1l1r1l2r2l3r3XOR(l1,r1)XOR(l2,r2)XOR(l3,r3)
定义 X O R ( l , r ) XOR(l,r) XOR(l,r) 为数组中 a l a_l al a r a_r ar 的异或和。
结果取模 998244353 998244353 998244353

题解

思维

运用拆位,考虑转化为二进制后的每一位。
①求解 ∑ 1 ≤ l 1 ≤ r 1 ≤ n X O R ( l 1 , r 1 ) \sum_{1 \leq l_1\leq r_1\leq n}XOR(l_1,r_1) 1l1r1nXOR(l1,r1)
显然的,求解有多少段区间异或和为 1 1 1
令右端点固定,枚举左端点。
由于异或的性质,我们可以通过前缀异或和判断一段区间的异或和。
定义 p r e i pre_i prei表示异或和
我们发现只有左端点和右端点的 p r e pre pre值不同才能做出贡献。添加一个数组 s u m i . , 0 / 1 sum_{i.,0/1} sumi.,0/1 ,统计前面的 p r e pre pre 值,我们就可以在 O ( n ) O(n) O(n) 的复杂度内求出结果。
·②求解 ∑ 1 ≤ l 1 ≤ r 1 ≤ l 2 ≤ r 2 ≤ n X O R ( l 1 , r 1 ) ∗ X O R ( l 2 , r 2 ) \sum_{1 \leq l_1\leq r_1\leq l_2 \leq r_2\leq n}XOR(l_1,r_1)*XOR(l_2,r_2) 1l1r1l2r2nXOR(l1,r1)XOR(l2,r2)
同样的,只有区间异或和为 1 1 1 的区间才能做出贡献。
我们需要维护有多少①中的区间前面还有一个不重叠的区间。在求解①的答案时,我们利用数组,计算有几个区间异或和为1的右端点在它前面。
我们发现,它又可以用一个前缀和数组来求出当前值。
③维护完了前两个数组,我们发现可以实现套娃操作,每次从上一个数组值中完成累加。

实现的一些优化

我们可以通过三维数组维护操作数、数位、当前位,三重循环求解
过程中转移是定向的,所以可以压缩一些数组。

参考代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,M=30;
const int mod=998244353;
ll s[N];
ll a[N],f[N];
int n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        ll x;
        scanf("%lld",&x);
        a[i]=a[i-1]^x;
        s[i]=1;
    }
    for(int k=1;k<=3;k++)
    {
        for(int j=0;j<=M;j++)     //拆位
        {
        	ll c[2]={0,0};          //在前缀和为0/1前能放k个区间
        	if(k==1)
        		c[1]=1;
            for(int i=1;i<=n;i++)
            {
                f[i]=(f[i]+(c[((a[i]>>j)&1)^1]<<j))%mod;    //统计方案数
                c[(a[i]>>j)&1]=(c[(a[i]>>j)&1]+s[i])%mod;
            }
        }
        for(int i=1;i<=n;i++)
        {
            s[i]=(s[i-1]+f[i])%mod;          //滚动数组
            f[i]=0;
//             cout<<s[i]<<endl;
        }
        f[0]=0;
    }
    
    printf("%lld",s[n]);
}

总结

将问题分解开来,发现小问题之间的联系,再通过关系快速求解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值