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

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)表示的是首项是 a a a,公差是 b b b c c c是一个除数, n n n是项数的和
对于第i位,最后一位叫第0位,从右往左数
我们要知道的就是 f ( x , z , 2 i , ⌊ ( y − x ) / z ⌋ + 1 ) f(x,z,2^i,\lfloor (y-x)/z \rfloor +1) f(x,z,2i,(yx)/z+1)的奇偶性
于是我们决定用最简单粗暴的方式,那就是求和。。
我们现在要求的等价与下面这个式子

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

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

就是一个式子:
( z ∗ n + x ) % c z % c \frac{(z*n+x)\%c}{z\%c} z%c(zn+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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值