异或约数和

首先,什么都不写,先要膜拜一下源神,%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
源神太强了!!!
为了不辜负源神整整给我讲了2个小时的题目,我打算好好写这篇博客。

题目:
定义 f(i) 为 i 的所有约数的异或和,给定 n(1≤n≤1014) ,求 f(1) xor f(2) xor f(3) xor…xor f(n) (其中xor表示按位异或)

输入
一行,输入一个整数n
输出
一行,一个整数为答案

输入样例
4
输出样例
7

样例解释:

f(1) = 1
f(2) = 1 xor 2 = 3
f(3) = 1 xor 3 = 2
f(4) = 1 xor 2 xor 4 = 7
1 xor 3 xor 2 xor 7 = 7

先补充一下位运算的知识点:
与运算:&
两者都为1为1,否则为0

或运算:|
两者都为0为0,否则为1

非运算:~
1取0,0取1

异或运算:^
两者相等为0,不等为1

题解:
先说一下异或运算的定义:
"^"异或符号是将两个数转化为二进制后同取0,不同取1;
(还不是因为我没学位运算 ( ̄ω ̄;) )
初步:O(n)的做法:
考试刚刚开始不到十分钟就有人说,这个题有个O(n)的算法,关键是还说特别容易想到,对于我这个连位运算都不会的菜鸡来说又是一大群神仙交流,现在想想其实如果会位运算真的特别容易想到,最最最最重要的事代码特短;
直接粘了:

#include <stdio.h>
int n,ans;
int main ()
{  
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	if(n/i&1)//如果n/i是奇数的话就异或,偶数就不变 
	ans^=i;
	printf("%d",ans);	
    return 0;      
}

超超超短,而且45分就到手了,,惊喜吧!
O(13)的做法:
再次感谢源神的悉心教导,%%%%%
现在我们是要求 i 的所有约数的异或和,所以首先是求异或和,当你知道O(n)的算法的时候,你就知道正解的一半了,但是当你在for循环的时候你就会发现(如图所示):
在这里插入图片描述
在这里插入图片描述

出现了很长一段相同的数字,而且当你推一下就会发现有固定的区间出现固定的数字。所以就想一下是不是可以在这里优化一下,一长段连续异或和的规律先说一下:
找规律:
1=1;
1 ^ 2=3;
1 ^ 2 ^ 3=0;
1 ^ 2 ^ 3 ^ 4=4;
1 ^ 2 ^ 3 ^ 4 ^ 5=1;
1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6=7;
1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7=0;
1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^8=8;
………………
很明显,四个数一次循环:1,n+1,0,n;
若(n % 4= =0) ,则f(n)=n;
若(n % 4= =1) ,则f(n)=1;
若(n % 4= =2) ,则f(n)=n+1;
若(n % 4= =3) ,则f(n)=0;
(相关正经的证明,可以手推一下)
设g(i)为第i数字的n/i(出现的次数)。
然后利用接个推理就可以直接O(1)的求出g(i-1)和g(j)然后他们互相异或就是这一个区间的异或和,至于说j就是(n/x)是什么,可以手推一下:
假设n=32,i=4;
则就又:4,8,12,16,20,24,28,32,一共8个数就是,这时候你就会惊奇的发现4—32的长度就是n/i,所以区间长度就是从i-1—n/x在计算他们的g()值即可。

AC代码:

#include<stdio.h> //提醒:51nod不让用万能库
long long n,ans=0;//瞟一眼范围就知道要开long long
inline long long  read()//记得快读也要开long long (菜鸡的我就在这上面WA掉了>_<)
{
	long long   s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch==-1)w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int main()
{
	n=read();	
	long long i=1;
	while(i<=n)//建议用while循环,否则i=(n/x)+1没办法写(源神的代码风格就是奇特)
	{
		long long x=n/i;//中间每个数出现的次数
		long long j=(n/x);//末尾端点
		//多说一句为什么j不直接写成i,是因为要下取整
		long long q,w;//记录g(i-1)和g(j)
		if((n/i)&1)//O(n)做法的核心代码
		{
			--i;//上个区间的端点
			if(i%4==0) q=i;
			else if(i%4==1) q=1;
			else if(i%4==2) q=i+1;
			else q=0;//处理g(i-1)
			if(j%4==0) w=j;
			else if(j%4==1) w=1;
			else if(j%4==2) w=j+1;
			else w=0;//处理g(j)
			ans^=q;
			ans^=w;//ans^q^w得出答案
		}
		i=(n/x)+1;//进入下个区间
	}
	printf("%lld\n",ans);
	return 0;
}

最后,又双叒叕%%%%%%%%一下源神,源神在强了tqltqltqltqltqltql
尽管用了两个小时但是还是很值得的,至少刷了一道省选题,开心ヾ(๑╹◡╹)ノ"

我不是天生的王者,但骨子里流着不低头的血液。 ——blng

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值