Loading

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 红包发红包

首先给出结论,答案为:

\[\frac{w}{2^k} \]

接下来不想看证明的就可以直接离开了。

根据离散型期望的公式 \(\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 这题然后再来做这一题的,然后就犯了一个非常愚蠢的错误:立方的期望不是期望的立方。

\[\frac{1^3+2^3}{2}\neq (\frac{1+2}{2}^3) \]

然后再化一下式子 \((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 点组计数

link

这种求一段区间(线段)的题通常是先考虑暴力枚举,显然这题枚举两个端点,然后只用求这条线段上的整点个数减二即可。然后我们发现这跟线段的位置没有关系,所以我们直接考虑枚举 \((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\)

那么我们就得到了我们要求的式子:

\[\begin{aligned} ans&=\sum_{i=1}^{m}[\gcd(i,k)=1]\sum_{j=1}^{n}[\gcd(i,j)=1]\\ &=\sum_{i=1}^{m}[\gcd(i,k)=1]\sum_{d|i}^{n}\mu(d)[n/d]\\ &=\sum_{d=1}^{n}\mu(d)[n/d]\sum_{i=1}^{[m/i]}[\gcd(di,k)=1]\\ &=\sum_{d=1}^{n}\mu(d)[n/d]\sum_{i=1}^{[m/i]}[\gcd(d,k)=1][\gcd(i,k)=1]\\ &=\sum_{d=1}^{n}[\gcd(d,k)=1]\mu(d)[n/d]\sum_{i=1}^{[m/i]}[\gcd(i,k)=1]\\ \end{aligned} \]

我们想要求一个 \(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\),那么我们考虑满足第二个要求的减去不满足第一个要求的:

\[\begin{aligned} g(n,k)&=g(n,q)-\sum_{i=1}^{[n/p]}[\gcd(pi,q)=1]\mu(pi)\\ &=g(n,q)-\sum_{i=1}^{[n/p]}[\gcd(i,q)=1]\mu(pi)\\ &=g(n,q)-\sum_{i=1}^{[n/p]}[\gcd(i,q)=1]\mu(pi)\\ &=g(n,q)-\mu(p)\sum_{i=1}^{[n/p]}[\gcd(i,k)=1]\mu(i)\\ &=g(n,q)+g(n/p, k)\\ \end{aligned} \]

第三行是因为 \(\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;
}
posted @ 2021-06-15 22:59  Semsue  阅读(233)  评论(0编辑  收藏  举报
Title