【bzoj4026】dC Loves Number Theory 可持久化线段树

题目描述

dC 在秒了BZOJ 上所有的数论题后,感觉萌萌哒,想出了这么一道水题,来拯救日益枯竭的水题资源。

给定一个长度为 n的正整数序列A,有q次询问,每次询问一段区间内所有元素乘积的φ(φ(n)代表1~n 中与n互质的数的个数) 。由于答案可能很大,所以请对答案 mod 10^6 + 777。 (本题强制在线,所有询问操作的l,r都需要 xor上一次询问的答案 lastans,初始时,lastans = 0)

输入

第一行,两个正整数,N,Q,表示序列的长度和询问的个数。

第二行有N 个正整数,第i个表示Ai.

下面Q行,每行两个正整数,l r,表示询问[l ^ lastans,r ^ lastans]内所有元素乘积的φ

输出

Q行,对于每个询问输出一个整数。

样例输入

5 10
3 7 10 10 5
3 4
42 44
241 242
14 9
1201 1201
0 6
245 245
7 7
6 1
1203 1203

样例输出

40
240
12
1200
2
240
4
4
1200
4



题解

可持久化线段树(个人觉得不应该叫做主席树,因为本题是区间线段树)

前置技能:HH的项链 强制在线

标准的做法是离线+树状数组,但是由于要在线处理,所以只能尽量使用可持久化数据结构。

我们还是按照那道题的思路,对于每个数,当它出现时就把它上一次出现的位置删除掉。

那么我们就可以使用可持久化区间线段树完成这样类似的操作。

对于每个数a[i],记录一下它上一个出现的位置last,在i对应的线段树中,把last位置的数-1,并把i位置的数+1。

这样在查询区间[l,r]的时候,在r对应的线段树中,查询[l,r]([l,n]同理)这段区间内数的和即为答案。

比较复杂,稍微理解一下~

就当是理解完了

回过头来看这道题,要求区间乘积的欧拉函数。

欧拉函数是什么请自行百度,怎么求请自行找博客。

这里还是简单说一下吧,$\varphi(x)=x\sum\limits_{prime(i)\& i|x}\frac{i-1}i$。

所以说对于这道题可以维护一个前缀乘积,然后只需要求出区间内的质因子即可。

使用类似于HH的项链的方法,对于一个位置i,枚举a[i]的质因子,如果它在之前last位置出现过,则将i位置对应的线段树的last位置的数除以$\frac{i-1}i$;在i位置的数乘以$\fraci-1{i}$。

然后使用可持久化线段树维护区间乘积,查询[l,r]时求出r对应的线段树中[l,r]([l,n]同理)的乘积,再乘上$\frac{mul[r]}{mul[l-1]}$(前缀积)即为答案。

需要筛逆元什么的,参见代码。

#include <cstdio>
#include <algorithm>
#define N 100010
#define M 2100000
using namespace std;
typedef long long ll;
const ll mod = 1000777 , k = 1000005;
ll a[N] , prime[M] , cnt , last[M] , ls[N * 80] , rs[N * 80] , tot , root[N] , inv[M] , sum[N * 80] , mul[N];
bool np[M];
void update(ll p , ll v , ll l , ll r , ll x , ll &y)
{
	y = ++tot , sum[y] = sum[x] * v % mod;
	if(l == r) return;
	ll mid = (l + r) >> 1;
	if(p <= mid) rs[y] = rs[x] , update(p , v , l , mid , ls[x] , ls[y]);
	else ls[y] = ls[x] , update(p , v , mid + 1 , r , rs[x] , rs[y]);
}
ll query(ll p , ll l , ll r , ll x)
{
	if(l >= p) return sum[x];
	if(r < p) return 1;
	ll mid = (l + r) >> 1;
	return query(p , l , mid , ls[x]) * query(p , mid + 1 , r , rs[x]) % mod;
}
int main()
{
	ll n , m , i , j , x , y;
	ll lastans = 0;
	scanf("%lld%lld" , &n , &m);
	for(i = 2 ; i <= k ; i ++ )
	{
		if(!np[i]) prime[++cnt] = i;
		for(j = 1 ; j <= cnt && i * prime[j] <= k ; j ++ )
		{
			np[i * prime[j]] = 1;
			if(i % prime[j] == 0) break;
		}
	}
	mul[0] = sum[0] = inv[1] = 1;
	for(i = 2 ; i < mod ; i ++ ) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	for(i = 1 ; i <= n ; i ++ )
	{
		scanf("%lld" , &a[i]) , mul[i] = mul[i - 1] * a[i] % mod , root[i] = root[i - 1];
		for(j = 1 ; prime[j] * prime[j] <= a[i] ; j ++ )
		{
			if(a[i] % prime[j] == 0)
			{
				if(!last[prime[j]]) update(i , (prime[j] - 1) * inv[prime[j]] % mod , 1 , n , root[i] , root[i]);
				else update(last[prime[j]] , prime[j] * inv[prime[j] - 1] % mod , 1 , n , root[i] , root[i]) , update(i , (prime[j] - 1) * inv[prime[j]] % mod , 1 , n , root[i] , root[i]);
				last[prime[j]] = i;
				while(a[i] % prime[j] == 0) a[i] /= prime[j];
			}
		}
		if(a[i] > 1)
		{
			if(!last[a[i]]) update(i , (a[i] - 1) * inv[a[i]] % mod , 1 , n , root[i] , root[i]);
			else update(last[a[i]] , a[i] * inv[a[i] - 1] % mod , 1 , n , root[i] , root[i]) , update(i , (a[i] - 1) * inv[a[i]] % mod , 1 , n , root[i] , root[i]);
			last[a[i]] = i;
		}
	}
	while(m -- ) scanf("%lld%lld" , &x , &y) , x ^= lastans , y ^= lastans , printf("%lld\n" , lastans = query(x , 1 , n , root[y]) * mul[y] % mod * inv[mul[x - 1]] % mod);
	return 0;
}
时间: 2024-08-26 03:23:36

【bzoj4026】dC Loves Number Theory 可持久化线段树的相关文章

[BZOJ4026]dC Loves Number Theory

试题描述 dC 在秒了BZOJ 上所有的数论题后,感觉萌萌哒,想出了这么一道水题,来拯救日益枯竭的水题资源. 给定一个长度为 n的正整数序列A,有q次询问,每次询问一段区间内所有元素乘积的φ(φ(n)代表1~n 中与n互质的数的个数) .由于答案可能很大,所以请对答案 mod 10^6 + 777. (本题强制在线,所有询问操作的l,r都需要 xor上一次询问的答案 lastans,初始时,lastans = 0) 输入 第一行,两个正整数,N,Q,表示序列的长度和询问的个数. 第二行有N 个正

BZOJ 4026 dC Loves Number Theory 分块+十字链表/可持久化线段树

题目大意:给定一个序列,多次询问某段区间乘积的φ值对1000777的模 我竟然卡过去了233333 将序列分块,记录fi,j表示第i块左端点到第j个点中出现的所有质数p的p?1p之积 每次询问[x,y],首先取出[x,y]区间内所有数的积,然后乘上fst,y(其中st是x后面第一个块端点所在块) 现在还剩[x,l[st]]部分没有统计 由于106以内的数分解得到的不同的质因数最多只有7个,因此暴力枚举零碎部分的质数即可 现在对于每个质数我们需要判断是否出现过 我们只需要判断这个质数下一次出现的位

bzoj 4026 dC Loves Number Theory (主席树+数论+欧拉函数)

题目大意:给你一个序列,求出指定区间的(l<=i<=r) mod 1000777 的值 还复习了欧拉函数以及线性筛逆元 考虑欧拉函数的的性质,(l<=i<=r),等价于 (p[j]是区间内所有出现过的质数) 那么考虑找出区间内所有出现过的质数,这思路和HH的项链是不是很像?? 由于此题强制在线,所以把树状数组替换成了主席树而已 原来我以前写的主席树一直都是错的......还好推出了我原来错误代码的反例 在继承上一个树的信息时,注意不要破坏现在的树 1 #include <cs

POJ-2104-K-th Number(可持久化线段树)

K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 55456   Accepted: 19068 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key inse

【POJ2104】K-th Number 主席树?函数式线段树?可持久化线段树?……反正是其中一个

题意:区间静态第K大. 题解: 可持久化线段树. 可持久化线段树: 基本思想:我们维护插入每个节点后的线段树. 朴素写法(MLE+TLE)我们对于每次插入,都复制一棵线段树而后插入,这样保证了"可持久化". 但是显然,时间复杂度和空间复杂度都是n^2的.233. 所以有了优化写法:我们发现每次的插入只有logn个节点被改变,所以只需要这些点新建,其它点都指向原来版本的节点就好了. 空间复杂度nlogn. 然后这道题是区间第K大,对于区间[a,b],插入到b时的线段树,节点的size-(

HDU 2665 Kth number 可持久化线段树

题意:给n个数和m个询问,询问l,r,k是从l~r中的第k小 思路:可持久化线段树的模板题 说下自己对可持久化线段树的理解吧 可持久化线段树的是可以保存历史版本的线段树,就是插进去第i个数的线段树的状态,这样我们可以通过state[r]-state[l-1]来得到state[l~r] 朴素做法就是维护n颗线段树,但是这样一般都会MLE 可持久化线段树利用了每次插入数只修改了线段树上一条链的特性来每次插入一个数只新建一条链来维护历史版本,空间复杂度O(n*logn+n*logn) 原树+新建的链

Codeforces 484E. Sign on Fence 可持久化线段树

大概题意: 给一数组a,问在某一区间L~R中,问对于连续的长为W的一段中最小的数字的最大值是多少. 显然可以转化成二分高度然后判断可行性的问题. 考虑到高度肯定为数组中的某一个值,将数组从大到小排序. 建n棵线段树,对于第 i 棵线段树,将 大于等于a[i] 的叶子的值设置为1,其他的叶子设置为0,问题就转化成了用线段树求某一区间中最长的连续的1的个数,这是一个线段树的经典问题,可以通过维护每个节点的 左边连续和,右边连续和,连续和的最大值 得到. 由于空间问题,不可能建立10^5棵线段树,考虑

学习笔记:可持久化线段树

1.前言 线段树,众所周知,在树中的每一个元素中,保存的是线段中的一段,所维护的内容或是最大最小值,或是和等等.可持久化线段树,属于可持久化数据结构中的一种,对于可持久化数据结构这个大知识,我暂时没有去研究,今天只讲其冰山一角. 2.概念 先讲”可持久化“的含义.”可持久化“表示我们当前在处理每个状态,而之前的状态即状态的历史版本全部能够存下来.可持久化线段树,实质上是多颗线段树,最简单的可持久化线段树的题目:求区间第k大.显而易见,求区间最大值的时候我们用普通的线段树就行了,第k大总不能一个个

主席树/函数式线段树/可持久化线段树

什么是主席树 可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 因此可持久化线段树也叫函数式线段树又叫主席树. 可持久化数据结构 在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本.这样的集合称为是可持久的. 实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间. 考虑一个持久集合S. 如图所示,对集合的