洛谷 P3312 [SDOI2014]數表 解題報告
P3312 [SDOI2014]數表
題目描述
有一張\(N*M\)的數表,其第\(i\)行第\(j\)列(\(1\le i \le n\),\(1 \le j \le m\))的數值為能同時整除\(i\)和\(j\)的所有自然數之和。給定\(a\),計算數表中不大於\(a\)的數之和。
輸入輸出格式
輸入格式:
輸入包含多組資料。
輸入的第一行一個整數\(Q\)表示測試點內的資料組數
接下來\(Q\)行,每行三個整數\(n\),\(m\),\(a\)(\(|a| \le 10^9\))描述一組資料。
輸出格式:
對每組資料,輸出一行一個整數,表示答案模\(2^{31}\)的值。
說明
\(1 \le N,M\le 10^5\) , \(1 \le Q \le 2*10^4\)
按道理就是先不管條件。
然後化簡式子得到了
\[\sum_{k=1}^{\min(n,m)}k\lfloor\frac{n}{k}\rfloor\lfloor\frac{m}{k}\rfloor\]
想想確實不能拿掉一些東西,否則沒法做。
想到有\(\mathbf {Id}=\sigma*\mu\)
於是把式子拆開
\[\sum_{k=1}^{\min(n,m)}\lfloor\frac{n}{k}\rfloor\lfloor\frac{m}{k}\rfloor\sum_{d|k}\sigma(d)\mu(\frac{k}{d})\]
或者換個方向反演也可以得到這個式子。
我們知道格子\((i,j)\)的值就是\(\sigma(gcd(i,j))\)
於是我們可以離線讀入,然後從小到大把\(\sigma\)加入字首和。
具體的,可以拿一個樹狀陣列維護\(\sum_{d|k}\sigma(d)\mu(\frac{k}{d})\)的字首和,然後每次查詢或者加一些東西進去就可以了。
複雜度\(O(n\log^2n+Q\sqrt n\log n)\)
Code:
#include <cstdio> #include <algorithm> const int N=1e5; std::pair <int,int> sigma[N+10]; int mu[N+10],v[N+10]; void init() { for(int i=1;i<=N;i++) mu[i]=1,sigma[i]=std::make_pair(i+1,i); sigma[1].first=1; for(int i=2;i<=N;i++) { if(!v[i]) mu[i]=-1; for(int j=i*2;j<=N;j+=i) { sigma[j].first+=i; if(!v[i]) { if((j/i)%i==0) mu[j]=0; else mu[j]*=-1; v[j]=1; } } } std::sort(sigma+1,sigma+1+N); } int min(int x,int y){return x<y?x:y;} struct node { int n,m,a,id; bool friend operator <(node n1,node n2){return n1.a<n2.a;} }qry[N+10]; int s[N+10],ans[N+10],pos=1,T; void add(int p,int d){while(p<=N)s[p]+=d,p+=p&-p;} int ask(int p){int sum=0;while(p)sum+=s[p],p-=p&-p;return sum;} void change(int d) { while(sigma[pos].first<=d&&pos<=N) { for(int i=sigma[pos].second;i<=N;i+=sigma[pos].second) add(i,sigma[pos].first*mu[i/sigma[pos].second]); ++pos; } } int main() { init(); scanf("%d",&T); for(int i=1;i<=T;i++) scanf("%d%d%d",&qry[i].n,&qry[i].m,&qry[i].a),qry[i].id=i; std::sort(qry+1,qry+1+T); for(int i=1;i<=T;i++) { change(qry[i].a); int n=qry[i].n,m=qry[i].m,sum=0; for(int l=1,r;l<=min(n,m);l=r+1) { r=min(n/(n/l),m/(m/l)); sum+=(n/l)*(m/l)*(ask(r)-ask(l-1)); } ans[qry[i].id]=sum&0x7fffffff; } for(int i=1;i<=T;i++) printf("%d\n",ans[i]); return 0; }
2018.11.26