loj 6344 :异或和(类欧几里得(a,b有负数情况的分析) + 分块 + 卡常技巧)

在这里插入图片描述


(太菜了,群友指点两小时才过)
按套路,将取模拆开,式子变成: ⨁ i = 1 n ( n − i ∗ ⌊ n i ⌋ ) \displaystyle\bigoplus_{i = 1}^n (n - i*\lfloor\frac{n}{i}\rfloor) i=1n(niin)
然后就可以按套路,按位考虑,计算每一位为 1 的数字的个数: ∑ i = 0 n ⌊ n − ⌊ n i ⌋ ∗ i 2 p ⌋ \displaystyle\sum_{i = 0}^{n}\lfloor\frac{n - \lfloor\frac{n}{i}\rfloor*i}{2^p}\rfloor i=0n2pnini
⌊ n i ⌋ \lfloor\frac{n}{i}\rfloor in进行分块,每个块间套类欧几里得,复杂度是 n log ⁡ 2 n \sqrt n \log^2n n log2n

⌊ n i ⌋ \lfloor\frac{n}{i}\rfloor in 分块在 i 较小时块比较小,块比较密集,对这些小的块可以直接暴力求解以降低常数,暴力到 4 e 7 4e7 4e7左右可以跑得飞快。

另一个小优化:对块间求类欧时,不需要每次都用 s o l v e ( r ) − s o l v e ( l − 1 ) solve(r) - solve(l - 1) solve(r)solve(l1),将式子改一下: ∑ i = 0 r − l ⌊ n − ⌊ n i ⌋ ∗ ( i + l ) 2 p ⌋ \displaystyle\sum_{i = 0}^{r - l}\lfloor\frac{n - \lfloor\frac{n}{i}\rfloor*(i + l)}{2^p}\rfloor i=0rl2pnin(i+l)

这样直接 s o l v e ( r − l + 1 ) solve(r - l + 1) solve(rl+1)即可,可以略微降低一些时间。

对于这题的类欧形式 a a a 是负的,若 a,b有负数,需要将 a ,b转正
a ∗ x + b c , a ∗ x + b \frac{a*x + b}{c},a*x + b cax+b,ax+b 本质上是一个等差数列,计算尾项然后把整个数列倒过来 a 就转正了,但倒过来之后 b 也可能是负数(以至于前几项都是负数),我用的究极笨比方法这时数列其实分成正负两部分,负数那部分可以倒过来,全部变成正的然后减掉那部分的贡献即可(那部分的贡献是负数)
详细见代码


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100;
ll n,ans[maxn];
ll f(ll a,ll b,ll c,ll n) {
	if(!a) return b / c * (n + 1);
	if(a >= c || b >= c)
		return f(a % c,b % c,c,n) + n * (n + 1) / 2 * (a / c) + (n + 1) * (b / c);
	ll m = (a * n + b) / c;
	return n * m - f(c,c - b - 1,a,m - 1);
}
ll cal(ll a,ll b,ll c,ll n) {
	//if(a >= 0 && b >= 0) 直接计算
	//if(b < 0 && a >= 0) 找到最后一个负数项,前几项负数倒过来取负变成正的然后减掉这部分贡献 
	if(a < 0 && b >= 0) {	//找到最后一个正数项,正数部分倒过来计算,负数取正然后扣掉这部分贡献 
		ll t = min(b / -a,n);
		if(t == n)
			return f(-a,b + t * a,c,n);
		else 
			return f(-a,b + t * a,c,t) - f(-a,-(b + (t + 1) * a),c,n - t);
	} else if(a < 0 && b < 0) {	//负数取正直接计算 然后扣掉这部分贡献 
		return -f(-a,-b,c,n);
	}
}
int main() {
	while(~scanf("%lld",&n)) {
		ll l,r,res = 0,cnt = 0;
		for(ll i = 1; i <= min(n,40000000ll); i++)
				cnt ^= n % i;
 
		for(l = 40000001ll; l <= n; l = r + 1) {
			r = n / (n / l);
			ll a = -(n / l),b = n + 1ll * a * l,tot = r - l;
			for(int i = 0; i <= 39; i++)
				ans[i] += cal(a,b,1ll << i,tot);
		}
		
		for(int i = 0; i <= 39; i++)
			res += ((ans[i] - 2 * ans[i + 1]) & 1) * (1ll << i);
			
		printf("%lld\n",cnt ^ res);
		return 0;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值