51nod780 异或约数和 整除分块

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/88781512

title

51 nod

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

analysis

首先考虑到枚举因数x,然后算出他是小于等于n的数字中x的倍数的个数,即 n / x ⌊n/x⌋ ,然后根据奇偶性判断是否要异或x。

这样复杂度是 O ( n ) O(n) 的,看到 n / x n/x 很容易想到数论分块。

然后问题就是如何快速查询连续区间的异或和。

s [ x ] = 1 2 3... x s[x]=1∧2∧3...∧x
那么区间 [ l , r ] [l,r] 的异或和就是 s [ r ] s [ l 1 ] s[r]∧s[l−1]

然后对于s数组打个表如下
在这里插入图片描述
可以发现在模4意义下是有规律的。然后就可以O(1)计算连续区间异或和了。

上面的都是来自wxyww,觉得他讲的很好。

反正我在比赛时,是想到了O(n)算法的,然后一个劲儿的想优化,问了邱神,邱神透露了一点“你会求连续自然数的异或和吗?”,然后康神就不让他说了。我想:这肯定是在暗示这道题和连续自然数异或和之间有规律,然后打表找规律,然后自己想出来的两个所谓优化,都被毙了。TAT

哎,不说伤心事了,放代码啦!

code

O(N)算法。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int main()
{
	ll n;read(n);
	int ans=0;
	for (int i=1;i<=n;++i)
		if (n/i&1)
			ans^=i;
	printf("%d\n",ans);
	return 0;
}

AC代码。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1, ch=getchar();
	while (!isdigit(ch) && ch^'-') ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
inline ll getans(ll x)
{
	if (x%4==1) x=1;
	else if (x%4==2) ++x;
	else if (x%4==3) x=0;
	return x;
}
inline ll ask(ll l,ll r)
{
	return getans(r)^getans(l-1);
}
int main()
{
	ll n;read(n);
	ll ans=0;
	for (ll l=1,r; l<=n; l=r+1)
	{
		r=n/(n/l);
		if (n/l&1)
			ans^=ask(l,r);
	}
	printf("%lld\n",ans);
	return 0;
}

appendix

另外,怎样打表才能更容易发现规律呢?找邱神啊!

#include<bits/stdc++.h>
using namespace std;
inline void work(int n)
{
	for (int i=1; i<=n; ++i)
	{
		if (n/i&1)
			cout<<i<<" ";
	}
}
int main()
{
	for (int i=1; i<=30; ++i)
		work(i);
	return 0;
}

结果如下:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/88781512