zoukankan      html  css  js  c++  java
  • 【bzoj4869】[Shoi2017]相逢是问候 扩展欧拉定理+并查集+树状数组

    题目描述

    Informatik verbindet dich und mich.
    信息将你我连结。
    B君希望以维护一个长度为n的数组,这个数组的下标为从1到n的正整数。一共有m个操作,可以分为两种:0 l r表示将第l个到第r个数(al,al+1,...,ar)中的每一个数ai替换为c^ai,即c的ai次方,其中c是输入的一个常数,也就是执行赋值ai=c^ai1 l r求第l个到第r个数的和,也就是输出:sigma(ai),l<=i<=rai因为这个结果可能会很大,所以你只需要输出结果mod p的值即可。

    输入

    第一行有三个整数n,m,p,c,所有整数含义见问题描述。
    接下来一行n个整数,表示a数组的初始值。
    接下来m行,每行三个整数,其中第一个整数表示了操作的类型。
    如果是0的话,表示这是一个修改操作,操作的参数为l,r。
    如果是1的话,表示这是一个询问操作,操作的参数为l,r。
    1 ≤ n ≤ 50000, 1 ≤ m ≤ 50000, 1 ≤ p ≤ 100000000, 0 < c <p, 0 ≤ ai < p

    输出

    对于每个询问操作,输出一行,包括一个整数表示答案mod p的值。

    样例输入

    4 4 7 2
    1 2 3 4
    0 1 4
    1 2 4
    0 1 4
    1 1 3

    样例输出

    0
    3


    题解

    扩展欧拉定理+并查集+树状数组

    扩展欧拉定理:

    通过各种证明可以得知,一个数n最多进行 log p 次操作后就会变为一个定值。

    我们先预处理出成为定值的步数,不断求欧拉函数,记录每次变成了什么数,直至p=1.

    此时需要继续迭代一层,作用后面讲。

    然后再用递推法预处理出c...^ai(j个c) mod phi...(p)(k个phi),按照公式用三维数组储存。

    这里需要注意:扩展欧拉定理仅在n≥phi(p)时成立,当n<phi(p)时,对应的解决方法就是不加等式右面phi(p)的一项(变成一个类似恒等式的东西)

    这时需要在求幂次的同时记录一下是否超过了phi(p),即判断两数相乘时是否超过phi(p)。

    这样预处理后可以开始处理操作了。

    用一个数组记录一下每个数操作了多少次,如果达到了能够使值不变的次数,则不再进行更新。而对于操作中的所有数暴力修改即可。

    因此需要一个数据结构,维护某一个数的下一个不能够使值不变(即操作次数没有达到某值)的数是什么。这个可以使用并查集来实现。

    然后由于要求和,所以再使用一个树状数组来维护前缀和。

    至于为什么求phi时要多迭代一项,具体原因比较复杂:

    先看一个例子:n=1,p=3,c=2,a[1]=0时,正解为0、1、2、1、1...,而错解为0、1、2、2、2...

    Why?这需要我们做这道题的根本思路。

    迭代至phi=1,是因为x%1=0。因此20≡0(mod 1)。

    如果按照错解的思路,下一步进行22^0 mod 1 mod 2=20 mod 1 mod 2,进而22^0 mod 1+1 mod 2=20 mod 1+1 mod 2

    看起来似乎很对,但是仔细想想可以观察到:0<1,不能按照扩展欧拉定理加上phi的一项!。

    所以应当是22^0 mod 1+1 mod 2=20 mod 1 mod 2,而这是不成立的。

    so,仅仅迭代到1是错误的,而多迭代一项(或特判)就能解决该问题。

    最后,总时间复杂度是O(nlognlog^2p),会TLE,究其原因是快速幂的logp的时间复杂度,具体优化方法:我们可以预处理出c^i mod phi...(p)和c^10000j mod phi...(p),然后找前半部分和后半部分。这样可以O(1)得到c的幂次。就能A了。

    #include <cstdio>
    #include <algorithm>
    #define N 50010
    using namespace std;
    typedef long long ll;
    int n , m , cnt[N] , tot;
    ll p , c , a[N] , sum[N] , fa[N] , base[30] , v[N][30][30] , pf[N][30] , pg[N][30];
    bool flag[N][30][30] , bf[N][30] , bg[N][30];
    ll pow(int y , int k , bool &flag)
    {
    	int tf = y % 10000 , tg = y / 10000;
    	flag = bf[tf][k] | bg[tg][k] | (pf[tf][k] * pg[tg][k] >= base[k]);
    	return pf[tf][k] * pg[tg][k] % base[k];
    }
    ll phi(ll x)
    {
    	ll i , ans = x;
    	for(i = 2 ; i * i <= x ; i ++ )
    	{
    		if(x % i == 0)
    		{
    			ans = ans / i * (i - 1);
    			while(x % i == 0) x /= i;
    		}
    	}
    	if(x != 1) ans = ans / x * (x - 1);
    	return ans;
    }
    void update(int x , ll a)
    {
    	int i;
    	for(i = x ; i <= n ; i += i & -i) sum[i] = (sum[i] + a) % p;
    }
    ll query(int x)
    {
    	int i;
    	ll ans = 0;
    	for(i = x ; i ; i -= i & -i) ans = (ans + sum[i]) % p;
    	return ans;
    }
    void init()
    {
    	int i , j , k;
    	for(i = 0 ; i <= tot ; i ++ )
    	{
    		pf[0][i] = 1;
    		for(j = 1 ; j <= 10000 ; j ++ )
    			bf[j][i] = bf[j - 1][i] | (pf[j - 1][i] * c >= base[i]) , pf[j][i] = pf[j - 1][i] * c % base[i];
    		pg[0][i] = 1 , pg[1][i] = pf[10000][i] , bg[1][i] = bf[10000][i];
    		for(j = 2 ; j <= 10000 ; j ++ )
    			bg[j][i] = bg[j - 1][i] | (pg[j - 1][i] * pg[1][i] >= base[i]) , pg[j][i] = pg[j - 1][i] * pg[1][i] % base[i];
    	}
    	for(i = 1 ; i <= n ; i ++ )
    	{
    		for(j = 0 ; j <= tot ; j ++ ) flag[i][0][j] = (a[i] >= base[j]) , v[i][0][j] = a[i] % base[j];
    		for(j = 1 ; j <= tot ; j ++ )
    			for(k = 0 ; k <= tot - j ; k ++ )
    				v[i][j][k] = pow(v[i][j - 1][k + 1] + (flag[i][j - 1][k + 1] ? base[k + 1] : 0) , k , flag[i][j][k]);
    	}
    }
    int find(int x)
    {
    	return x == fa[x] ? x : fa[x] = find(fa[x]);
    }
    int main()
    {
    	int i , opt , l , r;
    	scanf("%d%d%lld%lld" , &n , &m , &p , &c);
    	base[0] = p;
    	while(base[tot] != 1) tot ++ , base[tot] = phi(base[tot - 1]);
    	base[++tot] = 1;
    	for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &a[i]) , update(i , a[i]);
    	init();
    	for(i = 1 ; i <= n + 1 ; i ++ ) fa[i] = i;
    	while(m -- )
    	{
    		scanf("%d%d%d" , &opt , &l , &r);
    		if(opt == 0)
    		{
    			for(i = find(l) ; i <= r ; i = find(i + 1))
    			{
    				update(i , -v[i][cnt[i]][0]) , cnt[i] ++ , update(i , v[i][cnt[i]][0]);
    				if(cnt[i] == tot) fa[i] = find(i + 1);
    			}
    		}
    		else printf("%lld
    " , (query(r) - query(l - 1) + 2 * p) % p);
    	}
    	return 0;
    }
    
  • 相关阅读:
    测试:安装测试用例
    测试:界面测试
    软件项目管理:什么是baseline
    测试:fiddler使用
    android adb常用指令
    sqlite语句主页
    几条常见的数据库分页 SQL 语句
    linux下tomcat无法访问问题(换一种说法:无法访问8080端口)
    eclipse中svn的各种状态图标详解
    Tomcat项目部署方式
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/6952976.html
Copyright © 2011-2022 走看看