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

mac2024-05-06  31


(太菜了,群友指点两小时才过) 按套路,将取模拆开,式子变成: ⨁ 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)