[SDOI2014]数表
题目链接:[SDOI2014]数表
题意:
\(T\) 组数据,每次给出 \(n,m,a\) ,在 \(n*m\) 矩阵的每个位置上有一个数值,第 \(i\) 行第 \(j\) 列数值为能同时整除 \(i\) 与 \(j\) 的所有自然数之和,求 \(n*m\) 矩阵中所有不超过 \(a\) 的数值和。\((T\leq2*10^4;1\leq n,m\leq 10^5)\)
首先不考虑小于等于 \(a\) 的限制。
能同时整除 \(i\) 与 \(j\) 就是能整除 \(gcd(i,j)\),所有每个位置上的数即为 \(gcd\) 的因子和 \(\sigma(gcd(i,j))\)。
设\(f(x)\)表示 \(x\) 在 \(n,m\) 数表中出现次数。
\(f(x)=\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)=x]\)
老朋友了。。。
\(F(x) = \sum\limits_{x\vert d}f(d)=\left\lfloor\dfrac nx\right\rfloor\left\lfloor\dfrac mx\right\rfloor\)
\(f(x)=\sum\limits_{x\vert d}\mu(\dfrac dx)F(d) = \sum\limits_{x\vert d}\mu(\dfrac d x)\left\lfloor\dfrac nx\right\rfloor\left\lfloor\dfrac mx\right\rfloor\)
答案为每一个 \(x\) 约数和乘以出现次数。
\(Ans = \sum\limits_{i=1}^n\sigma(i)f(i)\)
\(\qquad=\sum\limits_{i=1}^n\sigma(i)\sum\limits_{i\vert d}\mu(\frac di)\left\lfloor\frac nd\right\rfloor\left\lfloor\frac md\right\rfloor\)
将枚举 \(i\) 的倍数改为枚举 \(d\) 的因数。
\(Ans=\sum\limits_{d=1}^n\left\lfloor\frac nd\right\rfloor\left\lfloor\frac md\right\rfloor\sum\limits_{i\vert d} \sigma(i)\mu(\frac di)\)
前面可以用整除分块,后面做前缀和。
设 \(g(x)=\sum\limits_{i\vert x}\sigma(i)\mu(\frac xi)\)
给 \(g\) 做个前缀和,分块的时候乘上前面的 \(\left\lfloor\frac nd\right\rfloor\left\lfloor\frac md\right\rfloor\)。
然后考虑有 \(a\) 的限制,则对于 \(g(x)\) 来讲当 \(x\) 的因子 \(i\) 满足\(\sigma(i)\ge a\) 时其对 \(g\) 没有贡献。
将询问离线按 \(a\) 升序排列,每当 \(a\) 增加时就将 \(\sigma(i)=a\) 的贡献加上,可以枚举倍数来更新 \(g\) 的值,由于 \(g\) 值在变化且需要快速求出前缀和,值需要用树状数组动态维护。
瓶颈复杂度为\(O(T\sqrt{n}log(n))\)。竟然可过?真神奇~
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#define QWQ cout<<"QwQ"<<endl;
#define uint unsigned int
#include <vector>
#include <queue>
#include <stack>
#include <map>
using namespace std;
const uint N=101010;
const uint qwq=303030;
const uint inf=0x3f3f3f3f;
uint T;
uint n,m;
uint big;
uint vis[N],p[N],cnt,mu[N];
uint f[N],g[N];
uint tree[N];
uint ans[N];
struct E{ uint zhi,id; } s[N];
inline bool cmpe(E aa,E bb) { return aa.zhi < bb.zhi; }
struct D{ uint x,y,a,id; } q[N];
inline bool cmpd(D aa,D bb) { return aa.a < bb.a; }
inline uint read() {
uint sum1 = 0, ff = 1; char c = getchar();
while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); }
while(c>='0'&&c<='9') { sum1 = sum1 * 10 + c - '0'; c = getchar(); }
return sum1 * ff;
}
void qiu(uint h) {
mu[1] = f[1] = 1;
for(uint i=2;i<=h;i++) {
if(!vis[i]) {
p[++cnt] = i; mu[i] = -1;
f[i] = i+1; g[i] = 1;
}
for(uint j=1; j<=cnt && i*p[j]<=h; j++) {
uint now = i*p[j];
vis[now] = 1;
if(i%p[j]==0) {
f[now] = f[i] * p[j] + g[i];
g[now] = g[i];
break;
}
f[now] = f[i] * (p[j]+1);
g[now] = f[i];
mu[now] = -mu[i];
}
}
for(uint i=1;i<=h;i++) s[i].id = i, s[i].zhi = f[i];
sort(s+1,s+h+1,cmpe);
}
inline void add(uint we,uint z) { for(uint i=we;i<=big;i+=(i&-i)) tree[i] += z; }
inline uint ask(uint we) { uint res = 0; for(uint i=we;i;i-=(i&-i)) res += tree[i]; return res; }
inline void insert(uint d) {
for(uint i=1;i*d<=big;i++) add(i*d,f[d]*mu[i]);
}
int main() {
qiu(big=N-1000);
T = read();
for(uint i=1;i<=T;i++) q[i].x = read(), q[i].y = read(), q[i].a = read(), q[i].id = i;
sort(q+1,q+T+1,cmpd);
uint nowa = 0, nows = 1;
for(uint i=1;i<=T;i++) {
while(nowa < q[i].a) {
nowa++;
while(s[nows].zhi <= nowa) insert(s[nows++].id);
}
n = q[i].x; m = q[i].y;
if(n>m) swap(n,m);
uint last = 0, now = 0;
for(uint l=1,r;l<=n;l=r+1) {
r = min( n/(n/l), m/(m/l) );
now = ask(r);
ans[ q[i].id ] += (n/l) * (m/l) * (now-last);
last = now;
}
}
uint tot = (1<<31);
for(uint i=1;i<=T;i++) cout<<ans[i]%tot<<"\n";
return 0;
}
细节还蛮多的,但是一A了,有被爽到~。。。
彩蛋:
由于此题答案要对\(2^{31}\)取模: