21.6 杂题
CF622F
自然数幂和,拉格朗日插值模板题。
关于自然数幂和为什么是 \(k+1\) 次多项式可以考虑归纳+普通幂转下降幂然后差分。
知道是 \(k+1\) 次多项式就很好做了,直接搞 \(k+2\) 个点(\((0,f(0))(1,f(1),\dots,(k+1,f(k+1)\))然后就可以 \(O(n)\) 球了。因为 \(i^k\) 是积性函数所以可以线性筛。
P4593
这题描述及其不清楚
首先这个 \(k\) 是不会变的,容易看出 \(k=m+1\)。那么接下来开始模拟题目的过程:首先先求一次 \(\sum_{i=1}^{n} i^k\) 然后减去那些空的,这里的复杂度即为 \(O(n+m)\),然后第一个空位即被干掉,设其位置为 \(p\),则再计算一遍 \(\sum_{i=p+1}^{n}i^k\) 减去所有后面的空位即可得到下一次释放的得分。那么我们就得到了一个答案的式子:\(\sum_{i=1}^{m}((\sum_{j=p_{i+1}}^{n}j^k)-(\sum_{j=i+1}^{m}p_j^k))\) 再加上第一次求得的和即为答案。而这可以用拉格朗日插值 \(O(m^2+nm)\) 做。
P3270
一个比较神仙的组合数学+dp。记 \(f_{i,j}\) 代表前 \(i\) 个课程,B神比 \(j\) 个人强的方案数,有 \(f_{i,j}=\sum\limits_{k=j}^{n-1}f_{i-1,k}\times {k\choose k-j}\times {n-k-1\choose r_i-1-(k-j)}\times g_i\)。
其中 \(g_i=\sum\limits_{j=1}^{u_i}{j}^{n-r_i}\times {(u_i-j)}^{r_i-1}\)。发现瓶颈在这里。考虑用拉格朗日插值优化它。不难发现(根据经验)这个东西是个 \(n\) 次式。有一点很恶心就是当 \(r_i=1\) 也就是它是第一名时要特判。我们发现不能用一般的 \(O(n)\) 方法求因为这个 \(g_0\) 没有定义。那么我们可以在 \([1,n+1]\) 找(因为原式显然是一个 \(n\) 次多项式)。这里找的时候有一个小 \(\text{trick}\):令 \(f(x)=\sum\limits_{j=1}^{x}{j}^{n-r_i}\times {(u_i-j)}^{r_i-1}\) 而不是 \(f(x)=\sum\limits_{j=1}^{x}{j}^{n-r_i}\times {(x-j)}^{r_i-1}\) 这样的话就可以 \(O(n)\) 预处理了。这里插值的写法也要变一下,变为:\(\sum\limits_{i=1}^{n+1}\frac{pre_{i-1}\times suf_{i+1}}{(i-1)!\times (n+1-i)!}\)。
P4463
不妨设 \(a_i<a_{i+1}\) 最后再乘上一个 \(n!\)。有一个很显然的dp方程:\(f_{i,j}=f_{i,j-1}+f_{i-1,j-1}\times j\),其中 \(f_{i,j}\) 代表前 \(i\) 个数在 \([1,j]\) 的范围内取的答案。可是我们发现这个第二维太大了,如果用矩阵乘法取优化的话大概率会T,考虑这个第二维是一个 \(2n+1\) 次的多项式所以可用拉格朗日插值优化。具体优化过程就很套路了。
P6271
莫反板子题
开始推式子:
\(f_d(x)=\sum\limits_{x=1}^{d}x^d[\gcd(x,d)=1]=\sum\limits_{x=1}^{d}x^d\sum\limits_{k|x,k|n}\mu(k)=\sum\limits_{k|n}\mu(k)k^d\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}i^d\)
我们发现后面那个式子 \(\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}i^d\) 是可以插值求的,就是说它是一个多项式。准确地说是一个 \(d+1\) 次多项式(证明考虑差分)。如果我们 \(O(d)\) 去插值的话就得到了一个 \(O(\sqrt{n}d)\) 的算法,但是这里因为 \(n\) 很大所以不行。主要是后面 \(\sum\limits_{i=1}^{\lfloor \frac{n}{k}\rfloor}i^d\) 的部分,设其为 \(g(\lfloor \frac{n}{k}\rfloor)\),我们发现要求的其实就是 \(\sum\limits_{k|n}\mu(k)k^dg(k)\)。这里看起来还是不能做,但是可以用一个还算常见的 \(\text{trick}\) 把多项式用其定义式表示,因为这里多次调用这个多项式只有自变量在变。然后自然就可以把多项式的系数提出来:\(\sum\limits_{i=0}^{d+1}g_i\sum\limits_{k|n}\mu(k)k^d(\frac{n}{k})^i\),而后面显然是个积性函数,直接求就好了。
考虑时间复杂度,算多项式定义式的复杂度为 \(O(d^2)\) 或 \(O(d^3)\)(拉格朗日插值或高斯消元),后面计算答案的复杂度为 \(O(wd)\),总的时间复杂度为 \(O(d^2+wd)\)。
CF995F
拉格朗日插值优化dp。定义子状态:\(dp_{x,i}\) 代表 \(x\) 号节点工资为 \(j\) 的方案数。显然有方程:\(dp_{x,i}=\Pi_{y\in son(x)}\sum_{j=1}^{i}dp_{y,j}\)。
而因为 \(d\) 很大所以不好求,发现式子是一个递推式求和求积的形式应该是个多项式,因为专一,所以考虑用拉格朗日插值来优化(套路),容易发现这个多项式是‘子树大小’次的(考虑归纳从儿子递推到父亲)。
P5104 红包发红包
首先给出结论,答案为:
接下来不想看证明的就可以直接离开了。
根据离散型期望的公式 \(\int_{-\infty}^{+\infty}xf(x)dx\),其中 \(f(x)\) 是概率密度函数满足 \(\int_{-\infty}^{+\infty}f(x)dx=1\)。这道题是一个均匀分布(好像是叫这个名字),\(f(x)=\frac{1}{n}(0\le x\le n),f(x)=0(x >n,x<0)\),于是得到答案为 \(\int_{0}^{w}\frac{x}{w}dx=\frac{w}{2}\)。
再回到这道题,设 \(f[i]\) 代表前 \(i\) 期望抢到的钱的和,则第i个人期望拿道 \(\frac{w-f[i-1]}{2}\) 的钱,有 \(f[i]=f[i-1]+\frac{w-f[i-1]}{2},f[1]=\frac{w}{2}\),所以 \(f[k]=\frac{w}{2^k}\)
P6154 游走
因为是等概率选择路径,所以假设总共有 \(G\) 条路径,则选择每条路径的概率为 \(\frac{1}{G}\)。那么假设有 \(n\) 条路径,每条路径的长度为 \(l_i\),则答案为 \(\sum_{i=1}^{n}\frac{l_i}{G}=\frac{\sum_{i=1}^{n}l_i}{G}\)。
而这些都可以通过一个拓扑来做。设 \(f_i\) 代表 \(i\) 为终点的路径总长度,则 \(f_v=\sum_{(u->v)}f_u+g_u\),\(g_v=(\sum_{(u->v)}g_u)+1\)。
P1297 单选错位
根据期望的线性性可以得到总的期望为做对每到题的期望的和。
我们发现一道题做没做对至于其和其前一个有关。形式化来说,第 \(i\) 道题做对的概率为 \(\frac{\min(a[i],a[i-1])}{a[i]a[i-1]}\)。
SP1026 FAVDICE - Favorite Dice
这道题有两种思考方式:
正推
设 \(f_i\) 代表扔出 \(i\) 个不同的数期望次数。显然有 \(f_1=1\)。
那么有再丢出一个不同的概率为 \(\frac{n-(i-1)}{n}\),其期望为 \(\frac{n}{n-(i-1)}\)。
倒推
设 \(f_i\) 代表已经扔出 \(i\) 个不同的数,扔出 \(n\) 的不同的数期望还需几次。那么有 \(f_n=0\)。
考虑你已经扔出了 \(i\) 个,那么接下来扔出已经扔出的数的概率为 \(\frac{i}{n}\),扔出没有过的数的概率为 \(\frac{n-i}{n}\)。于是有:\(f_i=\frac{n-i}{n}f_{i+1}+\frac{i}{n}f_i+1\)。代表如果扔出没有过的数就是 \(f_{i+1}\),已经扔过的数还需 \(f_i\)。这解的 \(f_i=f_{i+1}+\frac{n}{n-i}\)。
CF1042E Vasya and Magic Matrix
首先显然可以得到一个方程 \(f_{x,y}=\frac{\sum_{a_{x',y'}<a_{x,y}}f_{x',y'}+(x-x')^2+(y-y')^2}{sum}\),然后发现这么推是 \(O(n^2m^2)\) 的。考虑怎么优化,其实就是把式子拆开,然后就可以前缀和优化。时间复杂度 \(O(nm)\)。注意逆元要先预处理(否则会TLE on #23)。还有要注意相同颜色的点要一起转移。
P1365 WJMZBMR打osu! / Easy
挺水一道题,大概想了5min就想到了。
设 \(f_i\) 代表前 \(i\) 个字符期望得分,\(g_i\) 代表以 \(i\) 结尾的连续的 'o' 的期望长度。如果一个长度为 \(x\) 的一段 'o' 再加上一个 'o' 那么得分会加上 \(2x+1\)。
然后开始分类讨论:
- 当前这一位是 'o'。那么 \(f_i\) 显然会增加 \(2g_{i-1}+1\),\(g_i=g_{i-1}+1\)。
- 当前这一位是 'x'。那么 \(f_i\) 不会变,\(g_i=0\)
- 当前这一位是 '?'。那么 \(f_i\) 有 \(\frac{1}{2}\) 个概率增加 \(2g_{i-1}+1\),还有 \(\frac{1}{2}\) 的概率不会增加。同理 \(g_i\) 有 \(\frac{1}{2}\) 的概率会增加 \(1\),还有 \(\frac{1}{2}\) 的概率变为 \(0\)。
然后这一道题就做完了。
P1654 OSU!
我是先做完 P1365 这题然后再来做这一题的,然后就犯了一个非常愚蠢的错误:立方的期望不是期望的立方。
然后再化一下式子 \((x+1)^3-x^3=3x^2+3x+1\)。所以我们只需处理长度的期望和长度平方的期望。而 \((x+1)^2-x^2=2x+1\)。
所以假设 \(f,g,h\) 分别代表答案,长度的期望和长度平方的期望。
然后就可以转移 \(f_i=(1-p)f_{i-1}+p*(f_{i-1}+3*h+3*g+1)\),\(h=p*(h+2*g+1)\),\(g=p*(g+1)\)。
P4206
考虑先计算出猫在 \(u\),鼠在 \(v\) 时下一步猫会到哪。然后考虑 \(dp\),记 \(f[u][v]\) 代表猫在 \(u\),鼠在 \(v\) 时需要多少步。然后转移就先判断 \(u=v\),以及走一步或两步能否到达。否则猫就走两步,然后枚举鼠会怎么走递归下去。显然这个过程用记忆化搜索来实现更方便。
SNOI2019 数论
先强制满足第一个条件,则满足条件的数 \(x=A_i+kp,k\in[0,(T-1-A_i)/P]\)。
对于第二个条件可得 \(x=A_i+kp\equiv B_j\pmod Q\)。于是我们想要求 \(A_i+kp\bmod Q\)。我们发现这一定是一个环。然后我们考虑把所有的环找出来,预处理出环上 \(B_j\) 的个数,然后枚举 \(A_i\),一定能在某个环上找到 \(A_i\)。考虑给每个环固定一个起点,然后先跳到起点,再从起点跳若干次整环,再跳一次不整的。
这都可以直接在环上做前缀和完成(因为我们已经固定了起点),于是这道题就做完了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN=1e6+5;
template <typename T>
void read(T &x) {
T flag=1;
char ch=getchar();
for (; '0'>ch||ch>'9'; ch=getchar()) if (ch=='-') flag=-1;
for (x=0; '0'<=ch&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
x*=flag;
}
ll P, Q, n, m, T, A[MAXN], B[MAXN], mark[MAXN], vis[MAXN], nxt[MAXN], fa[MAXN], cnt[MAXN], num[MAXN], ans, id[MAXN];
vector<ll> sum[MAXN];
int main() {
memset(fa, -1, sizeof(fa));
read(P); read(Q); read(n); read(m); read(T);
T--;
for (ll i=1; i<=n; i++) read(A[i]);
for (ll i=1; i<=m; i++) read(B[i]), mark[B[i]]=true;
for (ll i=0; i<Q; i++) nxt[i]=(i+P)%Q;
for (ll i=0; i<Q; i++) {
if (fa[i]!=-1) continue;
fa[i]=i;
id[i]=num[i]=1;
if (mark[i]) cnt[i]++;
sum[i].push_back(cnt[i]);
for (ll j=nxt[i]; j!=i; j=nxt[j]) {
fa[j]=i;
id[j]=++num[i];
if (mark[j]) cnt[i]++;
sum[i].push_back(cnt[i]);
}
}
for (ll i=1; i<=n; i++) {//枚举每一种 A[i]
ll now=A[i]%Q;
ll f=fa[A[i]%Q];
ll lim=1ll*(T-A[i])/P;
if (id[now]!=1) {
ll tmp=min(lim, (ll)(num[f]-id[now]+1));
ans=ans+sum[f][id[now]+tmp-2]-sum[f][id[now]-2];
lim-=tmp;
}
ans=ans+1ll*lim/num[f]*cnt[f];
ans=ans+1ll*sum[f][lim%num[f]];
}
printf("%lld\n", ans);
return 0;
}
BZOJ3518 点组计数
这种求一段区间(线段)的题通常是先考虑暴力枚举,显然这题枚举两个端点,然后只用求这条线段上的整点个数减二即可。然后我们发现这跟线段的位置没有关系,所以我们直接考虑枚举 \((i,j)\) 代表和线段 \((0,0)\rightarrow(i,j)\) 同构的线段,然后这显然有 \((n-i)\times (m-i)\) 个。考虑怎么计数,然后有一个经典结论,一条线段 \((i,j)\) 显然是 \((i/\gcd(i,j),j/\gcd(i,j))\) 的 \(\gcd(i,j)\) 倍,而这个倍数显然就是整点个数,因为 \((i/\gcd(i,j),j/\gcd(i,j))\) 上没有整点。所以线段 \((i,j)\) 的整点个数为 \(\gcd(i,j)-1\),接下来就瞎求一求,搞个欧拉反演就好了。
然后这个数据范围就随便跑都能过,我就写了个 \(O(n\log{n})\) 的埃氏筛。
其实还有水平和竖直的线段,而这很好计数,就是 \({n\choose 3}\times m+{m\choose 3}\times n\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e5+5;
const ll mod=1e9+7;
template <typename T>
void read(T &x) {
T flag=1;
char ch=getchar();
for (; '0'>ch||ch>'9'; ch=getchar()) if (ch=='-') flag=-1;
for (x=0; '0'<=ch&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
x*=flag;
}
ll n, m, ans, prime[MAXN], phi[MAXN], cnt, cnt1[MAXN], cnt2[MAXN];
ll calc(ll x) {return x*(x-1)*(x-2)/6%mod; }
ll sum(ll x) { return x*(x+1)/2%mod; }
ll mul(ll x, ll y) { return x*y%mod; }
bool mark[MAXN];
void sieve() {
phi[1]=1;
for (int i=2; i<MAXN; i++) {
if (!mark[i]) {
prime[++cnt]=i;
phi[i]=i-1;
}
for (int j=1; j<=cnt&&prime[j]*i<MAXN; j++) {
mark[prime[j]*i]=true;
if (i%prime[j]==0) {
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
for (int i=1; i<=n; i++) for (int j=i; j<=n; j+=i) cnt1[i]=(cnt1[i]+(n-j))%mod;
for (int i=1; i<=m; i++) for (int j=i; j<=m; j+=i) cnt2[i]=(cnt2[i]+(m-j))%mod;
}
int main() {
read(n); read(m);
if (n>m) swap(n, m);
ans=mul(n, n)*mul(m, m)%mod;
ans=(ans-sum(m)*mul(n, n)%mod+mod)%mod;
ans=(ans-sum(n)*mul(m, m)%mod+mod)%mod;
ans=(ans+sum(n)*sum(m)%mod)%mod;
ans=(-ans+mod)%mod;
sieve();
for (int i=1; i<=n; i++) {
ans=(ans+phi[i]*cnt1[i]%mod*cnt2[i]%mod)%mod;
}
printf("%lld\n", (ans*2%mod+calc(n)*m%mod+calc(m)*n%mod)%mod);
return 0;
}
NOI2016 循环之美
首先 \(\frac{x}{y}\) 在 \(k\) 进制下的第 \(i\) 位为 \(x\times k^i \bmod y\)。所以 \(x\times k^p\equiv x\Leftrightarrow k^p\equiv 1 \pmod y\)。在最简分数(\(\gcd(x,y)=1\))的情况下,有 \(\gcd(k,y)=1\)。
那么我们就得到了我们要求的式子:
我们想要求一个 \(calc(n)=\sum_{i=1}^{n}[\gcd(i,k)=1]\),然后有 \(\gcd(i,k)=\gcd(i\bmod k,k)\),于是可以 \(O(k)-O(1)\) 求。
于是有 \(ans=\sum_{d=1}^{n}[\gcd(d,k)=1]\mu(d)[n/d]calc(m/d)\),然后发现这个东西可以数论分块。
接下来就不是很常规了,考虑要求一个 \([\gcd(i,k)=1]\mu(i)\) 的前缀和,设为 \(g(n,k)\)。设 \(p\) 是 \(k\) 的最小质因子,那么设 \(k=p^cq\),\(\gcd(p,q)=1\),那么 \([\gcd(i,p^cq)=1]\Leftrightarrow [\gcd(i,p)]=1\land [\gcd(i,q)]=1\),那么我们考虑满足第二个要求的减去不满足第一个要求的:
第三行是因为 \(\gcd(p,i)>1\) 的话 \(\mu(pi)\) 的值即为 \(0\)。
\(n=0\) 时直接返回 \(0\),\(k=1\) 时就是 \(\mu\) 的前缀和直接杜教筛即可。
考虑复杂度,\([n/p]\) 只会有 \(\sqrt n\) 中取值,然后只会做 \(\omega(k)\),所以复杂度是 \(O(\omega(k)\sqrt n+n^{\frac{2}{3}})\)。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2000005;
typedef long long ll;
template <typename T>
void read(T &x) {
T flag=1;
char ch=getchar();
for (; '0'>ch||ch>'9'; ch=getchar()) if (ch=='-') flag=-1;
for (x=0; '0'<=ch&&ch<='9'; ch=getchar()) x=x*10+ch-'0';
x*=flag;
}
ll n, m, k;
ll prime[MAXN], mu[MAXN], sum[MAXN], cnt;
ll ans;
bool mark[MAXN], check[2005];
ll f[2005], mn[MAXN];
map<ll, ll> mp[2005], mp_mu;
int gcd(int a, int b) {
return b?gcd(b, a%b):a;
}
void sieve() {
mu[1]=1;
for (int i=2; i<MAXN; i++) {
if (!mark[i]) prime[++cnt]=i, mu[i]=-1, mn[i]=i;
for (int j=1; j<=cnt&&prime[j]*i<MAXN; j++) {
mark[prime[j]*i]=true;
mn[prime[j]*i]=prime[j];
if (i%prime[j]==0) {
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for (int i=1; i<MAXN; i++) sum[i]=sum[i-1]+mu[i];
for (int i=1; i<=k; i++) {
f[i]=f[i-1];
if (gcd(i, k)==1) {
f[i]++;
check[i]=true;
}
}
}
ll get_sum(ll N) {
if (N<MAXN) return sum[N];
if (mp_mu.find(N)!=mp_mu.end()) return mp_mu[N];
ll ret=1;
for (ll i=2, j; i<=N; i=j+1) {
j=N/(N/i);
ret=ret-(j-i+1)*get_sum(N/i);
}
return mp_mu[N]=ret;
}
ll g(ll N, ll K) {
if (N==0) return 0;
if (K==1) return get_sum(N);
if (mp[K].find(N)!=mp[K].end()) return mp[K][N];
ll p=mn[K], c=0, tmp=K;
while (K%p==0) {
c++;
K/=p;
}
return mp[tmp][N]=g(N, K)+g(N/p, tmp);
}
ll calc(ll x) {
return ((ll)x/k*f[k]+f[x%k]);
}
int main() {
read(n); read(m); read(k);
sieve();
ll now=0;
for (ll i=1, j; i<=min(n, m); i=j+1) {
j=min(n/(n/i), m/(m/i));
ans=ans+(g(j, k)-g(i-1, k))*(n/i)*calc(m/i);
}
printf("%lld\n", ans);
return 0;
}
UVA11417 GCD
这应该是这7道题中最水的一道,因为它的范围只有 \(1 < N < 501\)。
如果你会欧几里得算法你应该就能A这道题,暴力枚举两个数,求其 \(gcd\),时间复杂度 \(O(TN^2\log{N})\)。
这个暴力的代码我就不给了。
P1390 公约数的和
这题题面说的有点迷,其实和上一题求的是一个东西。只不过数据范围变大了且没有了多组数据。
对要求的东西进行分析:设 \(f(n)=\sum\limits_{i=1}^{n-1}gcd(i,n)\),则题目要求即为 \(\sum\limits_{i=2}^{n}f(i)\)。
发现如果 \(gcd(i,j)=1\),则 \(gcd(ik,jk)=k\)。
我们可以枚举最大公约数 \(k\),然后\(f(n)=\sum\limits_{k|n}k \times \phi(k)\)(\(\phi\)是欧拉函数)。
通过提前将欧拉函数筛出来可以\(O(1)\)查询欧拉函数,但这样做的复杂度是\(O(N\sqrt{N})\),还是太慢。
我们考虑枚举公约数的时候把所有是其倍数的\(n\)都算出来,这样的\(n\)其实就是\(k,2k,3k, \cdots, floor(\frac{N}{k}) \times k\),答案会加上\(k \times \sum\limits_{i=1}^{floor(\frac{N}{k})}\phi(i)\),所以我们求一个欧拉函数的前缀和,这样对于每一个\(gcd:k\)就能做到\(O(1)\)了,总复杂度\(O(N)\)。
注意这道题\((a,a)\)这种不算,所以在计算时\(\phi(1)\)不算,要减掉。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 2000010;
long long prime[N], phi[N], tot;
long long sum[N];
bool mark[N];
long long n;
long long ans;
void get_phi() {
phi[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break;
mark[i * prime[j]] = 1;
phi[i * prime[j]] = phi[i] * ((i % prime[j] == 0) ? prime[j] : (prime[j] - 1));
if (i % prime[j] == 0) break;
}
sum[i] = sum[i - 1] + phi[i];
}
}
int main() {
cin >> n;
get_phi();
for (int i = 1; i <= n; i++) {
ans += (sum[n / i] - 1) * i;
}
cout << ans;
return 0;
}
UVA11424 GCD - Extreme (I)
这道题多了个多组数据,如果还按上述做法做的话复杂度是\(O(TN)\)会炸。
考虑提前预处理答案,做到\(O(1)\)查询。
根据上面的推到,可以设计一个类似埃式筛法的算法,枚举所有公约数\(i\),把他的倍数\(j\)的答案都加上\(i \times \Phi(\frac{j}{i})\),当然\(j \neq k\)。
这只是算出了\(f(n)\),还要做一遍前缀和就是题目所求。
这个算法的复杂度是\(O(N\log{N}+T)\)的,在解决多组数据的问题时胜过上述算法。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1000010;
long long prime[N], phi[N], ans[N], tot;
bool mark[N];
int n;
void get_ans() {
phi[1] = 1;
for (int i = 2; i <= 1000000; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (prime[j] * i > 1000000) break;
mark[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
for (int i = 1; i <= 1000000; i++) {
for (int j = i + i; j <= 1000000; j += i) {
ans[j] += i * (phi[j / i]);
}
}
for (int i = 1; i <= 1000000; i++) {
ans[i] += ans[i - 1];
}
}
int main() {
get_ans();
while (scanf("%d", &n) != EOF && n) {
printf("%lld\n", ans[n]);
}
return 0;
}
UVA11426 拿行李(极限版) GCD - Extreme (II)
SP3871 GCDEX - GCD Extreme
这两题和前一题基本一样,数据范围并没有太大的变化,用前面的代码就可以A。
P2398 GCD SUM
这题乘个\(2\)再把\((a,a)\)这种情况加上就行了。
P2568 GCD
这题把枚举所有约数变成枚举所有质数,求和变成求个数即可。
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 10000010;
int prime[N], phi[N], tot;
long long sum[N];
bool mark[N];
int n;
long long ans;
void get_phi() {
phi[1] = sum[1] = 1;
for (int i = 2; i <= n; i++) {
if (!mark[i]) {
prime[++tot] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= tot; j++) {
if (i * prime[j] > n) break;
mark[i * prime[j]] = 1;
phi[i * prime[j]] = phi[i] * ((i % prime[j] == 0) ? prime[j] : (prime[j] - 1));
if (i % prime[j] == 0) break;
}
sum[i] = sum[i - 1] + phi[i];
}
}
int main() {
cin >> n;
get_phi();
for (int i = 1; i <= tot; i++) {
ans += 2 * sum[n / prime[i]] - 1;
}
cout << ans;
return 0;
}