[ACNOI2022]卷爷函数(欧拉函数)

13 篇文章 0 订阅
8 篇文章 0 订阅

题目

题目背景
很多很多年以后,人们发明了时光机。他们决定派出人类中最有实力的人,回到过去,改写历史。于是 O U Y E \sf OUYE OUYE 踏上了征途。

不幸的是,那台时光机还没经过实地检验。所以它把 O U Y E \sf OUYE OUYE 传送到了早期人类的时代。这时的人类还没有发展出语言,甚至连口语也几乎为空白。 O U Y E \sf OUYE OUYE 想尽办法也没能跟他们进行任何交流。

可是, O U Y E \sf OUYE OUYE 捶了捶自己的背。早期人类见之,大惊骇,皆奇之,莫不争相模仿。自此之后,人类的智力水平就开始高速发展(这是 “捶背” 这一动作蕴含的「卷之意志」导致的);并且,由于不停地捶背,背变得越来越直,也就踏上了向直立人演变的漫长历程。

题目描述
众所周知, O U Y E \sf OUYE OUYE 是人类智能之祖,现代人类的血脉分散,已经只能赶上他的 1 0 1 0 100 10^{10^{100}} 1010100 分之一了。为了纪念他,希腊文明创造了象形字母 φ \varphi φ,就是 O U Y E \sf OUYE OUYE 捶背的情景(圆圈是躯干,后面是捶背的手,下面是站立的足)。

所以,原本这里有一个简单的线段树问题,但是为了纪念 O U Y E \sf OUYE OUYE,它变成了这样:对于正整数序列 a 1 , a 2 , a 3 , … , a n a_1,a_2,a_3,\dots,a_n a1,a2,a3,,an,支持以下三种操作。

  • 修改:将 a i a_i ai 的值改为 v    ( v ∈ N + ) v\;(v\in\N^+) v(vN+)
  • 加法查询:查询 φ ( ∑ i = l r a i ) \varphi(\sum_{i=l}^{r}a_i) φ(i=lrai) 。输出对 ( 1 0 9 + 7 ) (10^9+7) (109+7) 取模。
  • 乘法查询:查询 φ ( ∏ i = l r a i ) \varphi(\prod_{i=l}^{r}a_i) φ(i=lrai) 。输出对 ( 1 0 9 + 7 ) (10^9+7) (109+7) 取模。

其中 φ \varphi φ 是欧拉函数,即 φ ( n ) = ∑ i = 1 n [ gcd ⁡ ( i , n ) = 1 ] \varphi(n)=\sum_{i=1}^{n}\big[\gcd(i,n)=1\big] φ(n)=i=1n[gcd(i,n)=1]

数据范围与提示
n ⩽ 5 × 1 0 4 n\leqslant 5\times 10^4 n5×104 a i , v ⩽ 4 × 1 0 4 a_i,v\leqslant 4\times 10^4 ai,v4×104 。虽然 q ⩽ 1 0 5 q\leqslant 10^5 q105,但是修改操作不会超过 20000 20000 20000 个,且数据随机(操作类型除外)。

思路

拼盘题。先看加法查询。只能直接求和,然后询问 φ \varphi φ 值。暴力做,期望复杂度是多少呢?我们知道,较多的数求和应该服从正态分布,而不是等概率分布;但是为了简化,先假定为等概率吧。现在只需求出 x x x 取遍 [ 1 , n ] [1,n] [1,n] 时,求 φ ( x ) \varphi(x) φ(x) 的时间复杂度之和。

x x x 最大质因子为 p 1 p_1 p1,次大为 p 2 p_2 p2 。二者可以重复,即 p 2 p_2 p2 x p 1 x\over p_1 p1x 的最大质因子。那么求 φ ( x ) \varphi(x) φ(x) 的复杂度为 π ( max ⁡ { p 1 , p 2 } ) \pi(\max\{\sqrt{p_1},p_2\}) π(max{p1 ,p2}),其中 π ( n ) = n ln ⁡ n \pi(n)={n\over\ln n} π(n)=lnnn 为素数分布函数。

往大估,先计算 ∑ π ( p 1 ) \sum\pi(\sqrt{p_1}) π(p1 ) 。显然不超过 ∑ p π ( p ) n p ≈ n ln ⁡ n ∑ p 1 p ≈ O ( n n ln ⁡ 2 n ) \sum_{p}\pi(\sqrt{p}){n\over p}\approx{n\over\ln n}\sum_{p}{1\over\sqrt{p}}\approx\mathcal O({n\sqrt{n}\over\ln^2 n}) pπ(p )pnlnnnpp 1O(ln2nnn ) 。然后计算 ∑ π ( p 2 ) \sum\pi(p_2) π(p2) 。显然不超过 ∑ p π ( p ) n p 2 ≈ n ln ⁡ n ∑ p 1 p ≈ O ( n ln ⁡ ln ⁡ n ln ⁡ n ) \sum_{p}\pi(p){n\over p^2}\approx{n\over\ln n}\sum_{p}{1\over p}\approx\mathcal O({n\ln\ln n\over\ln n}) pπ(p)p2nlnnnpp1O(lnnnlnlnn)

所以平均值在 O ( n ln ⁡ 2 n ) \mathcal O({\sqrt{n}\over\ln^2 n}) O(ln2nn ) 级别,这就是暴力计算 [ 1 , n ] [1,n] [1,n] 内随机数的 φ \varphi φ 的期望复杂度。

查询大约在 n a 2 = 1 0 9 \frac{na}{2}=10^9 2na=109 左右的数,大约每次需要 100 100 100 次计算,也不算太多。

接下来看乘法查询。显然 φ ( ∏ i = l r a i ) = ∏ i = l r a i ( ∏ p ∣ a l a l + 1 ⋯ a r p − 1 p ) \varphi(\prod_{i=l}^{r}a_i)=\prod_{i=l}^{r}a_i\left(\prod_{p|a_la_{l+1}\cdots a_r} {p-1\over p}\right) φ(i=lrai)=i=lrai(palal+1arpp1),难点在于求解后一项。

比较自然的想法是,每个质因子分开讨论。套用数颜色的经典套路:包含自己,则提供贡献;包含相邻的,则减去一次贡献。最后则恰好只有一次贡献。

然而怎么计算贡献呢?发现实际上是 三维偏序,即 l , r l,r l,r 和时间 t t t 三个维度。所以离线之后 c d q \tt cdq cdq 分治 + + + 树状数组就行了。

吐槽:三维偏序竟然只能用离线后 c d q \tt cdq cdq 最方便!在线维护好像确实很难搞!这就是维度灾难吧……

a ⩽ 4 × 1 0 4 a\leqslant 4\times 10^4 a4×104 最多提供 D = 6 D=6 D=6 个质因子,所以时间复杂度 O ( n D log ⁡ 2 n ) \mathcal O(nD\log^2 n) O(nDlog2n),常数较大,所以要限制修改操作的个数。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <vector>
#include <set>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MOD = 1e9+7;
inline llong qkpow(llong b,int q){
	llong a = 1;
	for(; q; q>>=1,b=b*b%MOD)
		if(q&1) a = a*b%MOD;
	return a;
}

const int MAXN = 50005, MAXD = 6;
struct Node{
	int val; ///< positive if it's a modification
	int x, y; ///< work for x_q <= x_m, y_q >= y_m
	Node() = default;
	Node(int _v,int _x,int _y):val(_v),x(_x),y(_y){}
	bool operator < (const Node &t) const {
		if(x != t.x) return x > t.x;
		return val > t.val; // modification first
	}
};
Node node[MAXN*MAXD<<2]; int tot;

const int MAXA = 40005;
int doit[MAXA], undo[MAXA];
set<int> pos[MAXA];
void insert(int v,int x){
	set<int>::iterator it = pos[v].insert(x).first;
	node[++ tot] = Node(doit[v],x,x); // itself
	int pre = -1, suf = -1;
	if(it != pos[v].begin()) -- it, pre = *it, ++ it;
	if((++ it) != pos[v].end()) suf = *it;
	if((~pre) && (~suf)) // remove former one
		node[++ tot] = Node(doit[v],pre,suf);
	if(~pre) node[++ tot] = Node(undo[v],pre,x);
	if(~suf) node[++ tot] = Node(undo[v],x,suf);
}
void erase(int v,int x){
	set<int>::iterator it = pos[v].find(x);
	node[++ tot] = Node(undo[v],x,x); // itself
	int pre = -1, suf = -1;
	if(it != pos[v].begin()) -- it, pre = *it, ++ it;
	if((++ it) != pos[v].end()) suf = *it;
	if((~pre) && (~suf)) // add former one
		node[++ tot] = Node(undo[v],pre,suf);
	if(~pre) node[++ tot] = Node(doit[v],pre,x);
	if(~suf) node[++ tot] = Node(doit[v],x,suf);
	pos[v].erase(x); // the mission was over...
}

int n, ans[MAXN<<1];
struct BIT_multiplies{
	int c[MAXN];
	void clear(){ fill(c+1,c+n+1,1); }
	void clear(int qid){
		for(int i=qid; i<=n; i+=(i&-i))
			c[i] = 1; // for affected ones
	}
	int query(int qid){
		int res = 1;
		for(int i=qid; i; i&=(i-1))
			res = int(llong(res)*c[i]%MOD);
		return res;
	}
	void modify(int qid,int qv){
		for(int i=qid; i<=n; i+=(i&-i))
			c[i] = int(llong(c[i])*qv%MOD);
	}
};
BIT_multiplies ddg;
Node tmp[MAXN*MAXD<<2]; // helper
void cbddxyx(int l,int r){
	if(l == r) return ;
	const int mid = (l+r)>>1;
	cbddxyx(l,mid), cbddxyx(mid+1,r);
	for(int i=l,j=mid+1,k=l; i<=mid||j<=r; )
		if(j > r || (i <= mid && node[i] < node[j])){
			if(node[i].val > 0) // modification
				ddg.modify(node[i].y,node[i].val);
			tmp[k ++] = node[i ++]; // merge sort
		}
		else{
			if(node[j].val < 0) // only tackle queries!
				ans[-node[j].val] = int(ans[-node[j].val]
					*llong(ddg.query(node[j].y))%MOD);
			tmp[k ++] = node[j ++]; // merge sort
		}
	rep(i,l,r) node[i] = tmp[i], ddg.clear(tmp[i].y);
}

std::vector<int> primes;
bool isPrime[MAXA];
int least[MAXA], nxt[MAXA];
void sieve(int N = MAXA-1){
	memset(isPrime+2,true,N-1);
	for(int i=2,len=0; i<=N; ++i){
		if(isPrime[i]){
			primes.push_back(i), ++ len;
			least[i] = i, nxt[i] = 1;
		}
		for(int j=0; j!=len&&primes[j]<=N/i; ++j){
			isPrime[i*primes[j]] = false;
			least[i*primes[j]] = primes[j];
			if(i%primes[j] == 0){
				nxt[i*primes[j]] = nxt[i]; break;
			}
			nxt[i*primes[j]] = i; // just this
		}
	}
}

llong getPhi(llong x){
	long long phi = x;
	for(int i=0,len=primes.size(); i!=len; ++i){
		if(primes[i]*primes[i] > x) break;
		if(x%primes[i]) continue;
		phi -= phi/primes[i], x /= primes[i];
		while(!(x%primes[i])) x /= primes[i];
	}
	return (x == 1) ? phi : (phi-phi/x);
}

struct BIT_plus{
	long long c[MAXN];
	void clear(){ memset(c+1,0,n<<3); }
	llong query(int qid){
		long long res = 0;
		for(int i=qid; i; i&=(i-1)) res += c[i];
		return res;
	}
	void modify(int qid,int qv){
		for(int i=qid; i<=n; i+=(i&-i)) c[i] += qv;
	}
};
BIT_plus zxy; ///< Respected Brilliant Queen
int a[MAXN]; ///< original array
int main(){
	sieve(); // get primes
	for(int v=2; v!=MAXA; ++v) if(isPrime[v]){
		undo[v] = int(v*qkpow(v-1,MOD-2)%MOD);
		doit[v] = int(qkpow(undo[v],MOD-2));
	}
	n = readint(); int q = readint(); ddg.clear();
	rep(i,1,n) for(int v=a[i]=readint();
		v!=1; v=nxt[v]) insert(least[v],i);
	rep(i,1,n) zxy.modify(i,a[i]);
	rep(i,1,n) ddg.modify(i,a[i]); // keep cache
	for(int opt,l,r,i=1; i<=q; ++i){
		opt = readint(), l = readint(), r = readint();
		if(opt == 0){
			zxy.modify(l,r-a[l]);
			ddg.modify(l,int(r*qkpow(a[l],MOD-2)%MOD));
			for(; a[l]!=1; a[l]=nxt[a[l]])
				erase(least[a[l]],l);
			for(a[l]=r; r!=1; r=nxt[r])
				insert(least[r],l); // insert new
			ans[i] = -1; // do not output this
		}
		if(opt == 1){
			llong sum = zxy.query(r)-zxy.query(l-1);
			ans[i] = int(getPhi(sum)%MOD);
		}
		if(opt == 2){
			ans[i] = int(ddg.query(r)*
				qkpow(ddg.query(l-1),MOD-2)%MOD);
			node[++ tot] = Node(-i,l,r);
		}
	}
	ddg.clear(); cbddxyx(1,tot);
	rep(i,1,q) if(~ans[i]) printf("%d\n",ans[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值