【bestcoder #39】CD题解

来源:互联网 发布:mac 弹丸论破 言刃 编辑:程序博客网 时间:2024/06/07 00:13

Code

Accepts: 125 Submissions: 736
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
问题描述
wld有一个长度为n的序列a1..an
wld想要你给出下面这段c++代码的输出:
int calc()
{
int res=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
res+=gcd(a[i],a[j])*(gcd(a[i],a[j])-1);
res%=10007;
}
return res;
}
保证1≤n≤10000,1≤ai≤10000 (对于任意1≤i≤n)
输入描述
多组数据(最多10组)
对于每组数据:
第一行:一个数n表示序列的长度
第二行:n个数,依次为a1,a2,…,an
输出描述
对于每组数据:
输出calc函数的返回值
输入样例
5
1 3 4 2 4
输出样例
64
Hint
gcd(i,j)为两数的最大公因数。

两种做法,先说一种简单的:
①首先对于每一个数字i,都预处理出有多少个(sum[i])给定得数包含他作为因数,并将sum[i]平方。

②我们要考虑的问题是如果两个数都含有因数4,他们在2和4中都出现了,而只需要算4的这一次,为了处理这种情况,我们从大到小枚举因数:
最大的因数必然不会产生上述问题;
对于较小的数i,枚举他的倍数j,我们让sum[i]=sum[i]sum[j](注意这里都是平方),类似于容斥原理,就把不合题意的去掉了!

第二种做法用到了莫比乌斯反演:
如果是求gcd(i,j),我们可以利用n=φ(d)(dn),直接枚举因数k,每个k贡献φ(k)

注:
n=Σφ(d)(dn)的证明(from zyf-zyf)。
n个分数,分别是1n,2n,3n.....nn,把他们化简之后,每个分母一定是n的因数,也就是说k当分母的个数是φ(k),得证。

变成了求Σgcd(i,j)(gcd(i,j)1)后可以继续沿用上面的思想,之前是φ(d)(dn),我们假设现在变成了g(d)(dn),可以联想到莫比乌斯反演。
假设G(n)=g(d)(dn),那么根据刚才的定义G(n)=n(n1),通过莫比乌斯反演,我们可以通过G[n]来求出g(n)
g(n)=μ(nd)G(d)

#include <cstdio>#include <algorithm>#include <cstring>#include <cstdlib>#include <queue>#include <map>#include <vector>#include <iostream>#include <set>#include <cmath>#define mod 10007#define LL long long#define M 10000#include <vector>#define pb push_backusing namespace std;vector<int> c[M+5];int v[M+5],f[M],mu[M+5],tot,cnt[M+5],p[M],n;void Getmobius(){    mu[1]=1;    for (int i=2;i<=M;i++)    {        if (!v[i])        {            p[++tot]=i;            mu[i]=-1;        }        for (int j=1;j<=tot;j++)        {            if (i*p[j]>M) break;            v[i*p[j]]=1;            if (i%p[j]==0)            {                mu[i*p[j]]=0;                break;            }            else mu[i*p[j]]=-mu[i];        }    }}void Prepare(){    for (int i=2;i<=M;i++)    {        int x=i;        for (int j=1;j<=sqrt(i);j++)            if (x%j==0)            {                c[i].pb(j);                if (x/j!=j)                    c[i].pb(x/j);            }    }    for (int i=1;i<=M;i++)        for (int j=0;j<c[i].size();j++)            (f[i]+=mu[i/c[i][j]]*c[i][j]*(c[i][j]-1))%=mod;}int main(){    Getmobius();    Prepare();    while (scanf("%d",&n)!=EOF)    {        for (int i=1;i<=M;i++)            cnt[i]=0;        for (int i=1;i<=n;i++)        {            int x;            scanf("%d",&x);            for (int j=0;j<c[x].size();j++)                cnt[c[x][j]]++;        }        int ans=0;        for (int i=2;i<=M;i++)            (ans+=cnt[i]*cnt[i]%mod*f[i])%=mod;        printf("%d\n",(ans+mod)%mod);    }    return 0;}

Lucky

Accepts: 34 Submissions: 267
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
问题描述
wld有n个数(a1…an)
保证对于任意1≤i≤n,1≤ai≤n
wld有一个常数k保证2≤k≤2∗n
为了消除歧义保证k为奇数
他有m个询问
每个询问有参数l1,r1,l2,r2
对于每个询问你需要回答有多少个二元组(i,j)满足:
l1≤i≤r1且l2≤j≤r2且ai+aj=k
保证1≤n≤30000,1≤m≤30000
输入描述
多组数据(最多5组)
对于每组数据:
第一行:一个数n表示数的个数
接下来一行:一个数k表示wld的常数
接下来一行:n个数,依次为a1,a2,…an
接下来一行:一个数m表示询问数
接下来m行:四个数l1,r1,l2,r2表示这组询问的参数
输出描述
对于每组数据:
对于每个询问输出二元组的数目
输入样例
5
3
1 2 1 2 3
1
1 2 3 5
输出样例
2
Hint
a1 + a4 = 3
a2 + a3 = 3

莫队或者分块都可以。

莫队:
这里写图片描述
(类似于容斥原理)

分块:
预处理出f[i][j][k]表示第i块到第j块和为k的方案数,然后整块O(1)算,其他<n的部分暴力算。

1 0