数论什么的全都忘光了吧QAQ 做了几道简单的题练习一下。

bzoj1101: [POI2007]Zap

求有多少对数满足 gcd(x,y)=d, 1<=x<=a, 1<=y<=b

首先那个d肯定是要除下去的, 变成 gcd(x,y) = 1, 1<=x<=a, 1<=y<=b这样一个问题。

这样就变成了最经典的莫比乌斯反演, 设f(d)为 gcd(x,y) = d, 1<=x<=a, 1<=y<=b时满足要求的x,y的个数,F(d) = Σ(d|n)f(n) = [a/d] * [b/d], 那么f(1) = Σ μ(d) * [a/d] * [b/d]

复杂度是O(n)的, 但是多组询问会T,这时候想到了bzoj1257 CQOI2007]余数之和sum的思想:对于一个N, [n/d]的取值是sqrt(n)级别的(显然当d <= sqrt(n),只有sqrt(n)个d所以有sqrt(n)中取值, 当d > sqrt(n), n/d < sqrt(n)所以只有sqrt(n)中取值), 所以只要在 a/d或是b/d中有任何一个变化了以后再计算对f(1)的贡献就可以了,存一个μ的前缀和就行了。

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 50005
using namespace std;
int T, a, b, d;
int prim[MAXN], pp, isprim[MAXN], miu[MAXN];
int main(){
scanf("%d", &T);
miu[] = ;
for(int i = ; i <= ; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -;
for(int j = ; j <= pp && i*prim[j] <= ; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ) break;
miu[i*prim[j]] = -miu[i];
}
}
for(int i = ; i <= ; i ++) miu[i] += miu[i-];
while(T --){
scanf("%d%d%d", &a, &b, &d); a /= d, b /= d;
int now = , aa = a, bb = b, na = , nb = , ans = ;
while(){
if(na < nb){
ans += (miu[na]-miu[now-]) * aa * bb;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
ans += (miu[nb]-miu[now-]) * aa * bb;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
printf("%d\n", ans);
}
// system("pause");
return ;
}

bzoj2301: [HAOI2011]Problem b]

求有多少对数满足 gcd(x,y)=k, a<=x<=b, c<=y<=d

上一题然后随便容斥一下,,没什么好说的

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 50005
using namespace std;
int T, a, b, c, d, k;
int prim[MAXN], pp, isprim[MAXN], miu[MAXN];
int calc(int a, int b){ if(a <= || b <= ) return ;
int now = , aa = a, bb = b, na = , nb = , ans = ;
while(){
if(na < nb){
ans += (miu[na]-miu[now-]) * aa * bb;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
ans += (miu[nb]-miu[now-]) * aa * bb;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
return ans;
}
int main(){
scanf("%d", &T);
miu[] = ;
for(int i = ; i <= ; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -;
for(int j = ; j <= pp && i*prim[j] <= ; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ) break;
miu[i*prim[j]] = -miu[i];
}
}
for(int i = ; i <= ; i ++) miu[i] += miu[i-];
while(T --){
scanf("%d%d%d%d%d", &a, &b ,&c, &d, &k); a --; c --; a /= k, b /= k, c /= k, d /= k;
printf("%d\n", calc(b, d) - calc(a, d) - calc(c, b) + calc(a, c));
}
return ;
}

bzoj2820: YY的GCD

1<=x<=N1<=y<=Mgcd(x,y)为质数有多少对

天啊窝一定是世界上最后一个知道 [n/(x*y)] = [[n/x]/y] 的人!! QAQ

很显然可以直接枚举一个质数然后然后想原来的题那样做,但是T*600000*n的复杂度肯定无法接受。

ans = ∑(p为质数)∑(d) μ(d)*[n/pd]*[m/pd]

容易想到像之前那样把n和m分块处理一下, 可以做到T*600000*sqrt(n), 但显然还是无法接受QAQ

觉得p为质数这种东西放在外层循环肯定就必须枚举了, 但是如果放在内层可能有奇怪的事情发生?

ans = ∑(d)∑(p为质数) μ(d)*[n/pd]*[m/pd]

感觉很靠谱的样子!但是这里n除了除以一个p以外还除以了一个d, 让内层无法分块了, 所以考虑设一个数s = pd, 那么

ans = ∑(s)[n/s][m/s]∑(p为质数)μ(s/p)

随便算一算发现G(s) = ∑(p为质数)μ(s/p) 的这个函数是可以在线性筛的过程中算出来的!

所以问题转换为 ans = ∑(s)[n/s][m/s]G(s), 随便分块做一下就可以啦!

 #include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#define MAXN 10000005
using namespace std;
int T, n, m;
int prim[MAXN], pp, miu[MAXN];
long long G[MAXN];
bool isprim[MAXN];
int main(){
scanf("%d", &T);
miu[] = ;
for(int i = ; i <= ; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -, G[i] = ;
for(int j = ; j <= pp && i * prim[j] <= ; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ){G[i*prim[j]] = miu[i]; break;}
miu[i*prim[j]] = -miu[i]; G[i*prim[j]] = miu[i]-G[i];
}
}
for(int i = ; i <= ; i ++) G[i] += G[i-];
while(T --){
scanf("%d%d", &n, &m);
int now = , nn = n, mm = m, nen = n/nn, nem = m/mm; long long ans = ;
while(){
if(nen < nem){
ans += (G[nen]-G[now-])*nn*mm;
now = nen+; if(now > n) break;
nn = n/now;
nen = n/nn;
}else{
ans += (G[nem]-G[now-])*nn*mm;
if(nen == nem){
now = nen + ; if(now > n) break;
nn = n/now;
nen = n/nn;
}
now = nem + ; if(now > m) break;
mm = m/now;
nem = m/mm;
}
}
printf("%lld\n", ans);
}
return ;
}

[bzoj2705: [SDOI2012]Longge的问题]

求 sigma(gcd(i,n), 1<=i<=n<2^32)

很简单的题。ans = ∑(p|n) phi(n/p)*p

显然无法线性筛预处理phi, 那么就用 phi(n) =  n * ∏((pi-1)/pi)这个式子, dfs一遍就好啦。

对了这道题虽然它说 n <= 1<<32 但实际上没有n>=1<<31的情况诶!除了ans以外全部开int有机会从8ms降到4ms,,,,(好无聊

 #include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
int m, N, bin[], cbin[], cnt;
ll ans;
void dfs(int x, int s, int phin){
if(x == cnt+){
ans += phin*(m/s); return;
}
dfs(x+, s, phin);
s*=bin[x],phin*=(bin[x]-);
for(int i = ; i <= cbin[x]; i ++){
dfs(x+, s, phin);
s *= bin[x]; phin *= bin[x];
}
}
int main(){
cin >> N; m = N;
for(int i = ; i*i <= N; i ++)if(N%i==){
bin[++cnt] = i;
while(N%i==)cbin[cnt] ++, N/=i;
}
if(N)bin[++cnt]=N, cbin[cnt] = ;
dfs(, , );
printf("%lld\n", ans);
return ;
}

3529: [Sdoi2014]数表

有一张N×m的数表,其第i行第j列(1 < =i < = n,1 < =j < =m)的数值为能同时整除i和j的所有自然数之和。给定a,计算数表中不大于a的数之和。

感觉这题挺神的。

先不考虑a, 如何计算数表中所有数之和呢?

设F(x) 为能整除x的所有自然数之和, 这个可以线性筛时胡乱搞搞算出来。

ans = sigema{F(gcd(i,j)) }

= sigema(p) { sigema(d){miu(d)*[n/dp]*[m/dp]} * F(p)}

然后像bzoj2820: YY的GCD 那样, 试图把可以sqrt(n)计算的[n/dp]什么的挪到外层以降低复杂度

[n/dp] 和[m/dp] 这两项以外包含了/p的, 只要乘上p以后就可以挪到外边了

ans = sigema(p){ sigema(p|d){miu(d/p)*[n/d]*[m/d]} * F(p)}

= sigema(d){ [n/d]*[m/d] * sigema(p|d){miu(d/p) * F(p)} }

好啦!现在只需要能够预处理出来 G(x) = sigema(p|x){miu(x/p)*F(p)} 就可以分块前缀和啦!

因为约数的期望是O(logn)的所以只要暴力一下就可以做到O(nlogn)的复杂度啦!

现在考虑原问题。。。。。。。。。感觉a这个条件都要被遗忘了。

如果F(x) > a的话, 直接让F(x) = 0 就可以了! 所以只要把询问离线, 按a排一个序, 再把F(x)排一个序, 一个一个加进去, 加的时候用树状数组维护前缀和就可以了!好机智啊!

复杂度 O(nlog^2n + q*sqrt(n)*logn)

话说我知道现在还会在取模意义下做减法的时候不再加上一个mod ,,,,,,被自己蠢哭啦QAQ

 #include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define MAXN 100005
#define lowbit(x) (x&(-x))
using namespace std;
int T, N, prim[MAXN], pp, isprim[MAXN], miu[MAXN], cm[MAXN], fs[MAXN], A[MAXN], ans[MAXN];
inline void Add(int x, int c){
for(; x <= N; x += lowbit(x)) A[x] += c;
}
inline int Sum(int x){
int ret = ;
for(; x; x -= lowbit(x)) ret += A[x];
return ret;
}
struct FF{int f, x;}F[MAXN];
struct point{int n, m, a, num;}p[];
bool cmp(point x, point y){return x.a < y.a;}
bool cmpf(FF a, FF b){return a.f < b.f;}
inline void Insert(int x, int c){
for(int i = ; i*x <= N; i ++) Add(i*x, c*miu[i]);
}
int calc(int a, int b){
int now = , aa = a, bb = b, na = , nb = , ans = , pre = , la;
while(){
if(na < nb){
la = Sum(na);
ans += (la-pre) * aa * bb;
pre = la;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
la = Sum(nb);
ans += (la-pre) * aa * bb;
pre = la;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
return ans;
}
int main(){
scanf("%d", &T);
miu[] = F[].f = F[].x = ;
for(int i = ; i <= ; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -, F[i].f = i+, cm[i] = i, fs[i] = ;
for(int j = ; j <= pp && i*prim[j] <= ; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ){
miu[i*prim[j]] = ;
F[i*prim[j]].f = F[i].f + fs[i]*cm[i]*prim[j];
cm[i*prim[j]] = cm[i]*prim[j];
fs[i*prim[j]] = fs[i];
break;
}
miu[i*prim[j]] = -miu[i];
F[i*prim[j]].f = F[i].f*(+prim[j]);
cm[i*prim[j]] = prim[j], fs[i*prim[j]] = F[i].f;
}
F[i].x = i;
}
for(int i = ; i <= T; i ++) scanf("%d%d%d", &p[i].n, &p[i].m, &p[i].a), p[i].num = i, N = max(N, max(p[i].n,p[i].m));
sort(p + , p + T + , cmp);
sort(F + , F + +, cmpf);
int jj = ;
for(int i = ; i <= T; i ++){
while(jj <= && F[jj].f <= p[i].a){Insert(F[jj].x, F[jj].f); jj ++;}
ans[p[i].num] = calc(p[i].n, p[i].m)&((<<)-);
}
for(int i = ; i <= T; i ++) printf("%d\n", ans[i]);
return ;
}

BZOJ 2154 Crash的数字表格

求Σ[1<=i<=n]Σ[1<=j<=m]LCM(i,j) mod 20101009

这题没什么技巧,直接倒一下就可以了。

ans = sigema(p) {i*j/p ((i,j)=p) }

设S(x) = x*(x-1)/2

ans = sigema(p) { sigema(d){miu(d)*S(n/pd)*S(m/pd)*d2}*p }

然后分块套分块做一遍就好了 O(sqrt(n)*sqrt(n)) = O(n)

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
#define mod 20101009
#define MAXN 10000005
using namespace std;
int N, n, m, prim[MAXN], isprim[MAXN], pp;
ll sum[MAXN], miu[MAXN];
ll SS(ll x, ll y){
return (((x*(x+)/)%mod) * ((y*(y+)/)%mod) )%mod;
}
ll F(int a, int b){
int now = , aa = a, bb = b, na = , nb = ;
ll ans = ;
while(){
if(na < nb){
(ans += SS(a/now, b/now) * ((miu[na]-miu[now-]+mod)%mod) %mod )%=mod;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
(ans += SS(a/now, b/now) * ((miu[nb]-miu[now-]+mod)%mod) %mod ) %= mod;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
return ans;
}
ll calc(int a, int b){
int now = , aa = a, bb = b, na = , nb = ;
ll ans = ;
while(){
if(na < nb){
(ans += F(a/now, b/now) * ((sum[na]-sum[now-]+mod)%mod) %mod ) %=mod;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
(ans += F(a/now, b/now) * ((sum[nb]-sum[now-]+mod)%mod) %mod )%=mod;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
return ans;
}
int main(){
scanf("%d%d", &n, &m); N = min(n, m);
miu[] = ;
for(int i = ; i <= N; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -;
for(int j = ; j <= pp && i*prim[j]<=N; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ) break;
miu[i*prim[j]] = -miu[i];
}
}
for(int i = ; i <= N; i ++) sum[i] = (sum[i-]+i)%mod, miu[i] = (miu[i-]+miu[i]*i*i)%mod;
cout << calc(n, m)<<endl;
return ;
}

2693: jzptab

上一题的加强版, 1000组数据。

又是这样!窝打了很多东西后电脑崩了都没存下来QAQ

思路不难,考虑把上一题的那个式子继续化简, 像以前的方法一样通过把里面的那个多项式都乘上一个p或是用换元把pd换成S(其实这两种方法本质上是一样的), 然后就可以成功地把[n/p]这个非常棒的可以sqrt(n)搞出来的东西放到外层多项式了,现在只要考虑如何O(n)地预处理出内层多项式。

G(d) = sigema(i|d) {d/i * i2 * μ(i)} = d* sigema(i|d) {d*μ(d)}

用莫比乌斯反演推一下容易得出积性函数的因数和也是积性函数, 所以G(d)是积性函数, 线性筛一遍随便搞一搞就可以了

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define mod 100000009
#define MAXN 10000005
#define ll long long
using namespace std;
int T, N, M, prim[MAXN], pp, miu[MAXN], isprim[MAXN];
ll G[MAXN];
ll SS(ll a, ll b){return (((a*(a+)/)%mod)*((b*(b+)/)%mod))%mod;}
int calc(int a, int b){
int now = , aa = a, bb = b, na = , nb = , ans = ;
while(){
if(na < nb){
(ans += ((G[na]-G[now-]) * SS(aa, bb))%mod)%=mod;
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}else{
(ans += ((G[nb]-G[now-]) * SS(aa, bb))%mod)%=mod;
if(na == nb){
now = na+; if(now > a) break;
aa = a/now;
na = a/aa;
}
now = nb+; if(now > b) break;
bb = b/now;
nb = b/bb;
}
}
return ans;
}
int main(){
miu[] = ; G[] = ;
for(int i = ; i <= ; i ++){
if(!isprim[i]) prim[++ pp] = i, miu[i] = -, G[i] = (-i+mod)%mod;
for(int j = ; j <= pp && i*prim[j] <= ; j ++){
isprim[i*prim[j]] = ;
if(i%prim[j] == ){
G[i*prim[j]] = G[i]; break;
}
miu[i*prim[j]] = -miu[i];
G[i*prim[j]] = (G[i]*G[prim[j]])%mod;
}
}
for(int i = ; i <= ; i ++) G[i] = (G[i-] + G[i]*i)%mod;
scanf("%d", &T);
while(T--){
int a, b; scanf("%d%d", &a, &b);
printf("%d\n", (calc(a, b)+mod)%mod);
}
return ;
}

总结

上面的这些题其实都是一个问题换一下形式而已,可以起名叫做“gcd计数问题”?无非是看到要求一个gcd相关的东西, 然后不好做, 所以要莫比乌斯反演一下把(x,y)=d转化为(d|x 且 d|y), 这就是一个直接用除法取整可以解决的问题了,而除法取整有一个非常好的性质就是不同的 f(x) = x/i 的数量是 sqrt(x)级别的, 如果可以把他提出来且把它里面的东西的前缀和预处理出来就可以O(sqrt(n))地解决这个问题。有的时候 [x/ij] 被套在了内层于是只能O(sqrt(n))地算出内层部分, 不能接受, 于是就要想办法把[x/ij]换到外层, 内层整体乘上j就可以了,然后再考虑如何O(n)地计算被换到内层的部分的前缀和就可以了。

一些gcd计数问题的更多相关文章

  1. Project Euler 453 Lattice Quadrilaterals 困难的计数问题

    这是一道很综合的计数问题,对于思维的全面性,解法的过渡性,代码能力,细节处理,计数问题中的各种算法,像gcd.容斥.类欧几里德算法都有考察.在省选模拟赛中做到了这题,然而数据范围是n,m小于等于100 ...

  2. Cheerleaders UVA - 11806 计数问题

    In most professional sporting events, cheerleaders play a major role in entertaining the spectators. ...

  3. UVA10294项链和手镯(等价类计数问题)

    题意:       给你一串珠子(连接成了一个环),共有n个珠子组成,你有t种颜色,现在你来给这个珠子染色,问染成项链有多少种方法?染成手镯有多少种方法?在项链里,经过顺时针旋转后相同的算一个,在手镯 ...

  4. Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用

    OC中的三种定时器:CADisplayLink.NSTimer.GCD 我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下 @interface CADisplayLin ...

  5. iOS 多线程之GCD的使用

    在iOS开发中,遇到耗时操作,我们经常用到多线程技术.Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,只需定义想要执行的任务,然后添加到适当的调度队列 ...

  6. 【swift】BlockOperation和GCD实用代码块

    //BlockOperation // // ViewController.swift import UIKit class ViewController: UIViewController { @I ...

  7. 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...

  8. GCD的相关函数使用

    GCD 是iOS多线程实现方案之一,非常常用 英文翻译过来就是伟大的中枢调度器,也有人戏称为是牛逼的中枢调度器 是苹果公司为多核的并行运算提出的解决方案 1.一次性函数 dispatch_once 顾 ...

  9. hdu1695 GCD(莫比乌斯反演)

    题意:求(1,b)区间和(1,d)区间里面gcd(x, y) = k的数的对数(1<=x<=b , 1<= y <= d). 知识点: 莫比乌斯反演/*12*/ 线性筛求莫比乌 ...

随机推荐

  1. PHP修改表格(增删改)

    要求: 1.熟练shi用  post 和  get  传值        2. php嵌套在HTML中        3.熟练:if 语句(其他语句)的使用 --------------------- ...

  2. Js/Jquery获取iframe中的元素

    转载: Js/Jquery获取iframe中的元素 - - ITeye技术网站http://java-my-life.iteye.com/blog/1275205 在web开发中,经常会用到ifram ...

  3. List提取相同元素

    List<int> currentList = Cls_Data.SoruceDataIntses[key]; preList = currentList.Intersect(preLis ...

  4. October 27th Week 44th Thursday 2016

    The art of being wise is the art of knowing what to overlook. 智慧之道在于懂得该忽略什么. Always do your best. Wh ...

  5. 解决ftp上传connection reset错误

    切换到管理员,cmd下面执行:netsh advfirewall set global StatefulFTP disable MS对此的解释:https://technet.microsoft.co ...

  6. [网络流24题] 搭配飞行员(cogs 14)

    [问题描述]     飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾驶员.由于种种原因,例如相互配合的问题,有些驾驶员不能在同一架飞机上飞 ...

  7. 我的基于asp.net mvc5 +mysql+dapper+easyui 的Web开发框架(0)

    前些日子工作不太忙,自己开发了一个web框架用于以后快速开发,现在分享出来. 系统没有使用各种复杂的东西,也没有太多的层次,有兴趣的可以研究一下.

  8. Tips标签显示

    浏览地址 http://www.7yusou.com 鱼搜官网 下载到本地

  9. Jenkins持续集成 & .NET

    最近受累于测试环境每次发布都很麻烦,而且我们有多个测试环境,因此专门抽时间做了Jenkins的配置和研究. 折腾了两天终于绿灯以后,先来个截图,BlueOcean UI还是很nice的. 环境搭建 找 ...

  10. [翻译]通过使用正确的search arguments来提高SQL Server数据库的性能

    原文地址:http://www.sqlpassion.at/archive/2014/04/08/improving-query-performance-by-using-correct-search ...