codeforces-1114F-线段树练习
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-线段树练习的更多相关文章
- Please, another Queries on Array? CodeForces - 1114F (线段树,欧拉函数)
这题刚开始看成求区间$\phi$和了........先说一下区间和的做法吧...... 就是说将题目的操作2改为求$(\sum\limits_{i=l}^{r}\phi(a[i]))\%P$ 首先要知 ...
- Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论
Bash and a Tough Math Puzzle CodeForces 914D 线段树+gcd数论 题意 给你一段数,然后小明去猜某一区间内的gcd,这里不一定是准确值,如果在这个区间内改变 ...
- 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 ...
- Codeforces 938G 线段树分治 线性基 可撤销并查集
Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问 ...
- codeforces 1136E 线段树
codeforces 1136E: 题意:给你一个长度为n的序列a和长度为n-1的序列k,序列a在任何时候都满足如下性质,a[i+1]>=ai+ki,如果更新后a[i+1]<ai+ki了, ...
- Z - New Year Tree CodeForces - 620E 线段树 区间种类 bitset
Z - New Year Tree CodeForces - 620E 这个题目还没有写,先想想思路,我觉得这个题目应该可以用bitset, 首先这个肯定是用dfs序把这个树转化成线段树,也就是二叉树 ...
- D - The Bakery CodeForces - 834D 线段树优化dp···
D - The Bakery CodeForces - 834D 这个题目好难啊,我理解了好久,都没有怎么理解好, 这种线段树优化dp,感觉还是很难的. 直接说思路吧,说不清楚就看代码吧. 这个题目转 ...
- B - Legacy CodeForces - 787D 线段树优化建图+dij最短路 基本套路
B - Legacy CodeForces - 787D 这个题目开始看过去还是很简单的,就是一个最短路,但是这个最短路的建图没有那么简单,因为直接的普通建图边太多了,肯定会超时的,所以要用线段树来优 ...
- CodeForces 343D 线段树维护dfs序
给定一棵树,初始时树为空 操作1,往某个结点注水,那么该结点的子树都注满了水 操作2,将某个结点的水放空,那么该结点的父亲的水也就放空了 操作3,询问某个点是否有水 我们将树进行dfs, 生成in[u ...
- Linear Kingdom Races CodeForces - 115E (线段树优化dp)
大意: n条赛道, 初始全坏, 修复第$i$条花费$a_i$, m场比赛, 第$i$场比赛需要占用$[l_i,r_i]$的所有赛道, 收益为$w_i$, 求一个比赛方案使得收益最大. 设$dp[i]$ ...
随机推荐
- python正则表达式一[转]
原文:http://blog.jobbole.com/74844/ 作为一个概念而言,正则表达式对于Python来说并不是独有的.但是,Python中的正则表达式在实际使用过程中还是有一些细小的差别. ...
- python3之协程
1.协程的概念 协程,又称微线程,纤程.英文名Coroutine. 线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度.在一个线程中会有很多函数,我们把这些函数称为子程序, ...
- 数据库的连接使用——使用ADO.NET连接数据库
一.ADO.NET简介 ADO.NET是由很多类组成的一个类库,这些类库提供了很多对象,用来完成数据的连接.查询.插入.更新和删除等操作.其中主要包括以下对象: 1.Connection对象:用来连接 ...
- git命令行提交并且同步到远程代码库
远程代码库以github为例 1.打开 git bash 2.进入项目目录 cd /e/myGitProjects/test 3.提交到本地git仓库 git add -Agit commit -m ...
- lr使用linux Generator测试https莫名报 SSL protocol error when attempting to connect with host
接收一个性能测试任务,各种原因需要使用linux agent产生压力.诡异的事发生了,同样脚本windows回放成功,使用linux agent报如下错误,脚本回放失败. Action.c(33): ...
- Flask之数据库设置
4 数据库 知识点 Flask-SQLALchemy安装 连接数据库 使用数据库 数据库迁移 邮件扩展 4.1 数据库的设置 Web应用中普遍使用的是关系模型的数据库,关系型数据库把所有的数据都存储在 ...
- vmware+CentOs 6.9的安装步骤
一.安装步骤 linux分区 登录用户名和密码 登录用户名和密码后安装成功 二.远程控制Xshell的安装
- LeetCode(33):搜索旋转排序数组
Medium! 题目描述: 假设按照升序排序的数组在预先未知的某个点上进行了旋转. ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] ). 搜索一个给定的目标值 ...
- LeetCode(8):字符串转整数(atoi)
Medium! 题目描述: 实现 atoi,将字符串转为整数. 在找到第一个非空字符之前,需要移除掉字符串中的空格字符.如果第一个非空字符是正号或负号,选取该符号,并将其与后面尽可能多的连续的数字组合 ...
- 【ES】学习5-全文搜索
全文搜索两个最重要的方面是:相关性, 分析. 一旦谈论相关性或分析这两个方面的问题时,我们所处的语境是关于查询的而不是过滤. match:单个词查询 GET /my_index/my_type/_se ...