https://www.cnblogs.com/31415926535x/p/10391639.html

概述

这是一道用线段树维区间值的一道题,,题意很简单,,就是对于给定的一个序列,,初始时每个数的值不大于300,,,然后有两中操作,,一个是对区间[l, r]的每个数乘上以个数x,,一个是询问区间的乘积的欧拉函数值,,,

分析

首先对于第一个操作显然可以用线段树的延迟更新来完成,,

对于第二个操作,,我最先没考虑数据,,就想着直接维护区间的乘积,,对最后的区间乘积求欧拉函数值,,,但是,,,即使数据初始值很小,,但是多次累乘x后会爆ll,甚至是ull,,,

正解是这样的:

对于第一个操作,,每次都保存区间模mod的乘积,,,

对于第二个操作,,因为我们是求的区间积的欧拉函数值,也就是

\(MUL_{l,r} \times phi(Mul_{l, r}) = Mul_{l, r} \times \prod_{i=l}^j {prime[i]-1 \over prime[i]}\)

\(prime[i] 是指 Mul_{l, r} 的质因数\)

因为直接存 \(Mul_{l, r}\) 会爆掉,,而最后的结果实在mod下的数,,300以里的质数也只有62个,,所以可以标记出乘积的所有质因数,,用一个ll的数就行了(状压的思想),,对于任意一个区间的乘积的标记都可以用两个子节点的标记值的或运算得到,,同时标记值也只会因为乘上的那个数x而增加,,,公式里的除 \(prime[i]\) 也可以用逆元搞定,,这样这个操作就弄出来了,,

一开始我自己写爆了之后,就照着别人的思路一点一点的改,,莫名其妙的t,,一直以为是线段树写丑了,,,,后来看到一个人写的很简单但也过了,,,自己就重写了一遍过了,,数论+线段树的题第一次写,,学到很多,,尤其是状压的思想,,逆元,还有线段树作为一个维护的工具的使用,,,两个参数的返回可以使用 pair<int, ll> pii 型来返回,,

代码

#include <bits/stdc++.h>
//#include <iostream>
//#include <cstdio>
//#include <cstdlib>
//#include <string.h>
#define aaa cout<<233<<endl;
#define endl '\n'
#define pb push_back
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, ull> pii;
const int inf = 0x3f3f3f3f;//1061109567
const ll linf = 0x3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 4e5 + 5; //注意数据范围,,,因为这个wa了一发,,,,(为啥不是re233)
const int maxm = 2e5 + 5;
const ll mod = 1e9 + 7;
inline int read() //快读
{
int ans=0;
char ch=getchar();
while(!isdigit(ch))
ch=getchar();
while(isdigit(ch))
ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
inline ll pow_(ll a, ll b, ll p) //快速幂
{
ll ret = 1;
while(b)
{
if(b & 1) ret = (ret * a) % p;
a = (a * a) % p;
b >>= 1;
}
return ret;
}
//find all prime from 1 to 300
bool isprime[305];
int prime[70], tot = -1;
int inv_prime[70];
void init() //寻找300以内的质数及其质数的逆元
{
for(int i = 2; i <= 300; ++i)isprime[i] = false;
for(int i = 2; i <= 300; ++i)
{
if(!isprime[i])prime[++tot] = i, inv_prime[tot] = pow_(i, mod - 2, mod);
for(int j = 0; j <= tot && i * prime[j] <= 300; ++j)
{
isprime[i * prime[j]] = true;
if(i % prime[j] == 0)break;
}
}
}
ll find_prime(ll x) //寻找数x的质因数,存在则对应质数数组的index位位1,这样最后返回的值的二进制表示即为状压标记的结果
{
ull ret = 0;
for(int i = 0; i <= tot; ++i)if(x == x / prime[i] * prime[i])ret |= ((ll)1 << i);
return ret;
}
ll mull(ll a, ll b) //带模的乘法
{
return a * b % mod;
}
ll mul[maxn << 2], vis[maxn << 2], laz1[maxn << 2], laz2[maxn << 2];
int a[maxn];
#define mid ((l+r)>>1)
#define lc (rt<<1)
#define rc (rt<<1|1)
void pushup(int rt)
{
mul[rt] = mull(mul[lc], mul[rc]);
vis[rt] = vis[lc] | vis[rc];
return;
}
void pushdown(int rt, int llen, int rlen)
{
mul[lc] = mull(mul[lc], pow_(laz1[rt], llen, mod)); //更新乘积
mul[rc] = mull(mul[rc], pow_(laz1[rt], rlen, mod));
laz1[lc] = mull(laz1[lc], laz1[rt]); //更新子区间乘积的懒惰标记值
laz1[rc] = mull(laz1[rc], laz1[rt]);
laz1[rt] = 1; //恢复根区间乘积的懒惰标记值
vis[lc] |= laz2[rt]; //更新标记
vis[rc] |= laz2[rt];
laz2[lc] |= laz2[rt]; //更新子区间标记的懒惰标记值
laz2[rc] |= laz2[rt];
laz2[rt] = 0; //恢复根区间标记的懒惰标记值 return;
}
inline void build(int rt, int l, int r)
{
mul[rt] = vis[rt] = laz2[rt] = 0;
laz1[rt] = 1; //无标记时,乘积的标记的懒惰值为1,,,,,标记的为0,,
if(l == r)
{
mul[rt] = a[l];
vis[rt] = find_prime(mul[rt]); //叶子节点的标记值为其质因数出现的状压后的值
return;
}
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(rt);
return;
}
inline void update(int rt, int l, int r, int L, int R, int x, ll vx)
{
if(L <= l && r <= R)
{
mul[rt] = mull(mul[rt], pow_(x, r - l + 1, mod));
vis[rt] |= vx; //标记更新
laz1[rt] = mull(laz1[rt], x); //乘积的懒惰标记的更新
laz2[rt] |= vx; //标记的懒惰标记的更新
return;
}
if(laz1[rt] > 1)pushdown(rt, mid - l + 1, r - mid);
if(laz2[rt])pushdown(rt, mid - l + 1, r - mid);
if(R <= mid)update(lc, l, mid, L, R, x, vx);
else if(L > mid)update(rc, mid+1, r, L, R, x, vx);
else update(lc, l, mid, L, R, x, vx), update(rc, mid+1, r, L, R, x, vx);
// if(L <= mid)update(lc, l, mid, L, R, x, vx);
// if(R > mid)update(rc, mid + 1, r, L, R, x, vx);
pushup(rt);
return;
}
inline pii query(int rt, int l, int r, int L, int R)//询问区间的乘积值和标记值
{
if(L <= l && r <= R)
{
return pii(mul[rt], vis[rt]);
}
if(laz1[rt] > 1)pushdown(rt, mid - l + 1, r - mid);//乘积的懒惰标记大于一说明待更新区间
if(laz2[rt])pushdown(rt, mid - l + 1, r - mid); //标记的懒惰值非零说明待更新
if(R <= mid)return query(lc, l, mid, L, R); //询问区间再左子区间时,,递归询问左子区间
if(L > mid)return query(rc, mid + 1, r, L, R);
pii a = query(lc, l, mid, L, R); //a为佐子区间的值
pii b = query(rc, mid + 1, r, L, R); //b为侑子区间的值
return pii(mull(a.first, b.first), (a.second | b.second));//总区间的值为左右子区间的乘积的积和标记的或
}
ll phi(ll mul, ull vis) //利用标记指求其欧拉函数值
{
for(int i = 0; i <= tot; ++i)
if((vis >> i) & 1)
mul = mull(mul, mull(prime[i] - 1, inv_prime[i]));
return mul;
}
int main()
{
// freopen("233.in" , "r" , stdin);
// freopen("233.out" , "w" , stdout);
// ios_base::sync_with_stdio(0);
// cin.tie(0);cout.tie(0);
int n, q;
//n = read(); q = read();
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; ++i)a[i] = read();
init(); //初始化找出300以内的所有素数,和对应的逆元
build(1, 1, n); //建树
char s[20];
int l, r, x; while(q--)
{
scanf("%s", s);
l = read();r = read();
if(s[0] == 'M')
{
//l = read(); r = read(); x = read();
//scanf("%d", &x);
x = read();
update(1, 1, n, l, r, x, find_prime(x));//更新操作,最后一个参数是x的质因数的标记值
}
else
{
//l = read(); r = read();
pii tmp = query(1, 1, n, l, r); //返回区间值的乘积和他的标记
// cout << tmp.first << "---" << tmp.second << endl;
// ll ans = 1;
// for(int i = l; i <= r; ++i)ans = mull(ans, query(1, i, i).first);
// cout << ans << endl;
printf("%lld\n", phi(tmp.first, tmp.second));
}
} return 0;
}

感想

看来只做简单题是学不到新东西的,,,难题虽然难,,熬夜弄了两天wa了好几发但最后弄出来还是很有意义的,,,

同时多看看别人的代码也很有感触,,学到很多好东西,,

(end)

codeforces-1114F-线段树练习的更多相关文章

  1. Please, another Queries on Array? CodeForces - 1114F (线段树,欧拉函数)

    这题刚开始看成求区间$\phi$和了........先说一下区间和的做法吧...... 就是说将题目的操作2改为求$(\sum\limits_{i=l}^{r}\phi(a[i]))\%P$ 首先要知 ...

  2. Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论

    Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论 题意 给你一段数,然后小明去猜某一区间内的gcd,这里不一定是准确值,如果在这个区间内改变 ...

  3. Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals) Problem E (Codeforces 831E) - 线段树 - 树状数组

    Vasily has a deck of cards consisting of n cards. There is an integer on each of the cards, this int ...

  4. Codeforces 938G 线段树分治 线性基 可撤销并查集

    Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问 ...

  5. codeforces 1136E 线段树

    codeforces 1136E: 题意:给你一个长度为n的序列a和长度为n-1的序列k,序列a在任何时候都满足如下性质,a[i+1]>=ai+ki,如果更新后a[i+1]<ai+ki了, ...

  6. Z - New Year Tree CodeForces - 620E 线段树 区间种类 bitset

    Z - New Year Tree CodeForces - 620E 这个题目还没有写,先想想思路,我觉得这个题目应该可以用bitset, 首先这个肯定是用dfs序把这个树转化成线段树,也就是二叉树 ...

  7. D - The Bakery CodeForces - 834D 线段树优化dp···

    D - The Bakery CodeForces - 834D 这个题目好难啊,我理解了好久,都没有怎么理解好, 这种线段树优化dp,感觉还是很难的. 直接说思路吧,说不清楚就看代码吧. 这个题目转 ...

  8. B - Legacy CodeForces - 787D 线段树优化建图+dij最短路 基本套路

    B - Legacy CodeForces - 787D 这个题目开始看过去还是很简单的,就是一个最短路,但是这个最短路的建图没有那么简单,因为直接的普通建图边太多了,肯定会超时的,所以要用线段树来优 ...

  9. CodeForces 343D 线段树维护dfs序

    给定一棵树,初始时树为空 操作1,往某个结点注水,那么该结点的子树都注满了水 操作2,将某个结点的水放空,那么该结点的父亲的水也就放空了 操作3,询问某个点是否有水 我们将树进行dfs, 生成in[u ...

  10. Linear Kingdom Races CodeForces - 115E (线段树优化dp)

    大意: n条赛道, 初始全坏, 修复第$i$条花费$a_i$, m场比赛, 第$i$场比赛需要占用$[l_i,r_i]$的所有赛道, 收益为$w_i$, 求一个比赛方案使得收益最大. 设$dp[i]$ ...

随机推荐

  1. transform 图标旋转,IE8、IE7不兼容

    要将图标旋转,只需使用transform的rotate以及transition即可完成旋转的动画效果.ease 规定慢速开始,然后变快,然后慢速结束的过渡效果;   ease-in 规定以慢速开始的过 ...

  2. slf4j的简单用法以及与log4j的区别

    之前在项目中用的日志记录器都是log4j的日志记录器,可是到了新公司发现都是slf4j,于是想着研究一下slf4j的用法. 注意:每次引入Logger的时候注意引入的jar包,因为有Logger的包太 ...

  3. UML入门[转]

    访问权限控制 class Dummy { - private field1 # protected field2 ~ package method1() + public method2() } Al ...

  4. 【API】检查进程是否存在 - CreateToolhelp32Snapshot

    1 学习目标 今天静态逆向mydocument病毒时,看到病毒代码为了防止自身被调试会先检测杀毒软件和调试工具的进程是否存在.如果没有杀毒软件则释放真正的病毒文件,提前熟悉一下枚举进程的反汇编代码. ...

  5. 使用 GDebi 默认代替 Ubuntu 软件中心

    GDebi,一个安装 Debian 可执行文件的专用程序.它极其轻量,且专注于安装 .deb 文件,可以自动解决依赖问题,比原生的好用.GDebi 最有用的功能是它也可以为你展示出将要安装的程序的依赖 ...

  6. 在Ubuntu中通过update-alternatives切换软件版本

    update-alternatives是ubuntu系统中专门维护系统命令链接符的工具,通过它可以很方便的设置系统默认使用哪个命令.哪个软件版本,比如,我们在系统中同时安装了open jdk和sun ...

  7. mysql之 innobackupex备份+binlog日志的完全恢复【转】

    前言: MySQL的完全恢复,我们可以借助于完整的 备份+binlog 来将数据库恢复到故障点. 备份可以是热备与逻辑备份(mysqldump),只要备份与binlog是完整的,都可以实现完全恢复. ...

  8. java并发编程系列二:原子操作/CAS

    什么是原子操作 不可被中断的一个或者一系列操作 实现原子操作的方式 Java可以通过锁和循环CAS的方式实现原子操作 CAS( Compare And Swap )  为什么要有CAS? Compar ...

  9. PHP CLI模式下的多进程应用

    作者: Laruence(   ) 本文地址: http://www.laruence.com/2009/06/11/930.html 转载请注明出处 PHP在很多时候不适合做常驻的SHELL进程, ...

  10. IntelliJ IDEA创建JavaWeb工程及配置Tomcat部署

    步骤: 在WEB-INF 下创建classes 和 lib 两个文件夹 右上角一个蓝色的按钮... Modules选项卡,Paths下的配置...输出路径选择classes Dependencies选 ...