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. DSO windowed optimization 代码 (4)

    5 "step"计算 参考<DSO windowed optimization 公式>,计算各个优化变量的增加量. 公式再写一下: \[\begin{align} \b ...

  2. 在iOS 开发中用GDataXML(DOM方式)解析xml文件

    因为GDataXML的内部实现是通过DOM方式解析的,而在iOS 开发中用DOM方式解析xml文件,这个时候我们需要开启DOM,因为ios 开发中是不会自动开启的,只有在mac 开发中才自动开启的.我 ...

  3. 恶意PDF文档分析记录

    0x1 PDF是什么 PDF(便携式文件格式,Portable Document Format)是由Adobe Systems在1993年用於文件交换所发展出的文件格式. 因为PDF的文件格式性质广泛 ...

  4. Python中【__all__】的用法

    Python中[__all__]的用法 转:http://python-china.org/t/725 用 __all__ 暴露接口 Python 可以在模块级别暴露接口: __all__ = [&q ...

  5. springboot系列一、springboot产生背景及介绍

    一.为什么用Springboot 长期以来 Java 的开发一直让人所诟病: ·Java 项目开发复杂度极其高: · Java 项目的维护非常困难: · 在云时代如何实现项目的快速部署以及快速启动: ...

  6. dubbo系列三、架构介绍及各模块关系

    一.整体设计 图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代 ...

  7. Java中利用Scanner键入的字符串与其他字符串的比较

    利用Scanner获取到键入的字符串与其他字符串作比较时,如果直接用关系运算符 == 比较,得到的结果总是false,因为实际比较的是两个变量引用的内存地址: 而要比较其内容是否相等,可以使用Obje ...

  8. Project Euler Problem 10

    Summation of primes Problem 10 The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of ...

  9. saltStack的event接口通过mysql数据库接收SaltStack批量管理日志

    event是一个本地的ZeroMQ PUB Interface,event是一个开放的系统,用于发送信息通知salt或其他的操作系统.每个event都有一个标签.事件标签允许快速制定过滤事件.除了标签 ...

  10. 浏览器桌面通知Notification实践

    一言不合就上图: 最近常常在浏览器看到这样的消息推送,还有QQ.com的推送,现在我对这个不了解,不知道叫消息自动推送对不对,这个时chrome浏览器的截图,出现在右下角,其他浏览器的样式可能有些微差 ...