当前位置: 代码迷 >> 综合 >> poj3495 Bitwise XOR of Arithmetic Progression 等差数列异或和
  详细解决方案

poj3495 Bitwise XOR of Arithmetic Progression 等差数列异或和

热度:92   发布时间:2023-10-29 07:09:42.0

upd 2019.3.26

修复了之前些的一些bug
然后加了些解释
应该更好理解
基本上就是大面积重构23333
顺带一提,这个做法基本就是类欧

题意

就是求等差数列异或和
x表示首项,y表示末项,z表示公差

怎么做?

等差数列的和,我会啊 !(首项+末项)*项数/2
然而这题并不是这样的
由于异或的特殊性,于是我们可以一位一位来考虑
我们定义一个函数f(a,b,c,n)f(a,b,c,n)f(a,b,c,n)表示的是首项是aaa,公差是bbbccc是一个除数,nnn是项数的和
对于第i位,最后一位叫第0位,从右往左数
我们要知道的就是f(x,z,2i,?(y?x)/z?+1)f(x,z,2^i,\lfloor (y-x)/z \rfloor +1)f(x,z,2i,?(y?x)/z?+1)的奇偶性
于是我们决定用最简单粗暴的方式,那就是求和。。
我们现在要求的等价与下面这个式子

∑i=0n?1?x+z?ic?\sum_{i=0}^{n-1}\lfloor \frac{x+z*i}{c} \rfloori=0n?1??cx+z?i??
这个问题就是类欧的模板题啊
显然地,式子可以变成下面这一个
∑i=0n?1?xc?+(?zc?i)+?x%c+z%c?ic?\sum_{i=0}^{n-1}\lfloor\frac{x}{c}\rfloor+(\lfloor\frac{z}{c}\rfloor i)+\lfloor \frac{x\%c+z\%c*i}{c} \rfloori=0n?1??cx??+(?cz??i)+?cx%c+z%c?i??
那么前面两个部分,我们可以一开始就算出来,这个没有关系
那么问题就只剩下了后面这一个
∑i=0n?1?x%c+z%c?ic?\sum_{i=0}^{n-1}\lfloor \frac{x\%c+z\%c*i}{c} \rfloori=0n?1??cx%c+z%c?i??
这么怎么求呢?
我们可以把他看做是一个关于iii的,截
x%cc\frac{x\%c}{c}cx%c?,斜率为z%cc\frac{z\%c}{c}cz%c?的函数
那么就是问这个函数,在x=nx=nx=n之前,他的下面xxx轴的上面有多少个整点,令每一列的整点数为g(i)g(i)g(i)
不失一般性,我们可以把截距设为bbb,斜率设为aaa
这样大家看下面的图也好看一些
容易知道,他的图像是这样的
要注意的是,n这个地方的点是不能取到的
拿了POPOQQQ的一张图
左边的是没有模过,右边是已经模过了的
这里写图片描述
右边这个图显然有一个特点,那就是截距肯定是小于111
于是最diao的地方就来了
我们旋转坐标轴
没错,黄色的那个就是了
这个坐标轴取的原点是(n,g(n))(n,g(n))(n,g(n))
我们的问题就等价与求这个新坐标轴下包含了多少个点
容易知道,这样做的话,点数既没有变多,也没有变少
于是我们考虑讨论这条直线
斜率显然是之前的倒数(感受一下),带过去也就是cz%c\frac{c}{z\%c}z%cc?
确实是感受一下就好
那么考虑一下截距是什么怎么做呢?
方法也很简单,你就考虑使用新的坐标轴就可以了
我们可以用那条已知的线
明显地,我们可以知道他第一个点,也就是原直线在n处的取值

然后我们还知道他的斜率,那么求出截距也就是轻而易举的事情了
把式子化一化就可以得到

就是一个式子:
(z?n+x)%cz%c\frac{(z*n+x)\%c}{z\%c}z%c(z?n+x)%c?
就可以变成一个子问题了
递归求解就可以了

如果再观察一下,发现,z和c是类似与辗转相除地进行的,于是有复杂度保证,就是log层就结束了

为什么这个算法会得到改进呢?
因为我们发现,如果一个直线,他是十分陡峭的,我们可以轻松地算出一大片答案,但是如果比较平缓那就很难搞了
于是我们可以通过旋转坐标轴使得这条直线又变得陡峭起来,这样子又可以一次算出一堆答案了
从而剩下很多复杂度,这个方法可以学习

然后就没有啦!完结散花

搞了一个晚上,大概把所有疑问搞懂了,感觉很舒服啊
如果还有在哪里讲得有问题,可以留言啊

CODE:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
LL x,y,z;//x是首项 y是不能超过y z是公差 
LL f (LL a,LL b,LL c,LL n)//首项 公差 c 项数 
{
    if (n==0) return 0;LL ans=(b/c)*n*(n-1)/2+(a/c)*n;ans=ans+f((b*n+a)%c,c,b%c,((b%c)*n+(a%c))/c);return ans;
}
int main()
{
    while (scanf("%I64d%I64d%I64d",&x,&y,&z)!=EOF){
    LL ans=0;for (LL u=0;u<32;u++)ans=ans|((f(x,z,(1LL<<u),(y-x)/z+1)&1)<<u);printf("%I64d\n",ans);}return 0;
}