1. 程式人生 > >BZOJ3817:Sum(類歐幾里得)

BZOJ3817:Sum(類歐幾里得)

傳送門

題意:

給定正整數n,r,求:

d=1n(1)dr

題解:

有點像類歐幾里得。

只需要知道:

d=1ndr%2

又因為

x%2=xx22

那麼問題轉化為求

d=1ndx

顯然這是一條從原點出發的直線,要求的是它的下半部分的整數點。

有一個結論是如果這條直線的斜率大於1,那麼先減掉1的斜率並加上這個斜率帶來的一個直角三角形的貢獻,這個新得到的直線覆蓋的整點即為原來直線還沒有加上的點。

類似於歐幾里得,現在我們得到了一個斜率小於1的直線,那麼考慮翻轉這個座標系來列舉y軸,發現又變成了一個子問題,這樣迭代只會進行 O(log

n)次。

具體的實現由於精度問題,我寫的long double類並不能通過,所以要用long long類做分子分母來表示,記現在直線的斜率為ax+bc(<1),要延伸到長度為n的位置。

先計算出在n的時候直線可以覆蓋的點數t,並把nt加入貢獻中,此時我們多加入了上半部分的三角形,再把這個直線翻轉,長度變為t,斜率變為cax+b=c(axb)a2rb2,減去這部分的貢獻即可,這部分可以遞迴實現,程式碼非常的短。

Fastio部分可以忽略掉


#include<bits/stdc++.h>
typedef long long ll;
typedef long double
db; using namespace std; const int R_LEN=(1<<18)|1; struct Fast_io{ char ibuf[R_LEN],obuf[R_LEN],*s,*t,*wt; int buf[50]; Fast_io(){s=ibuf,t=ibuf; wt=obuf;} ~Fast_io(){fwrite(obuf,1,wt-obuf,stdout);} inline void print(char c){ (wt==(obuf+R_LEN)) && (fwrite(obuf,1
,R_LEN,stdout),wt=obuf); *wt++=c; } template<typename T> inline void W(T x){ if(x<0){print('-');x=-x;} if(!x){print('0');return;} while(x){buf[++buf[0]]=x%10;x/=10;} while(buf[0])print(buf[buf[0]--]+'0'); } inline char getc(){ (s==t) && (t=(s=ibuf)+fread(ibuf,1,R_LEN,stdin)); return (s==t)?-1:*s++; } inline int rd(){ char ch=getc(); int i=0,f=1; while(!isdigit(ch)){if(ch=='-')f=-1; ch=getc();} while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getc();} return i*f; } }io; int T,n,r;db R,mxlen; inline ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} inline ll solve(ll a,ll b,ll c,ll len){ if(!len) return 0; ll t=gcd(a,gcd(b,c)); a/=t; b/=t; c/=t; t=(a*R+b)/c; ll sum=(len*(len+1)>>1)*t; b-=c*t; t=(a*R+b)*len/c; return sum+t*len-solve(a*c,-b*c,a*a*r-b*b,t); } int main(){ for(T=io.rd();T;T--){ n=io.rd(),r=io.rd(); R=sqrt(r); int t=(int)(R+0.5); if(t*t==r){io.W((t&1)?((n&1)?-1:0):n);io.print('\n');continue;} int odd_cnt=solve(1,0,1,n); odd_cnt-=2*solve(1,0,2,n); io.W(n-2*odd_cnt),io.print('\n'); } }