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. Python 入门基础13 --模块与包

    本节内容: 一.模块及使用 1.模块及使用 2.起别名.from导入 3.自执行与模块 二.包的使用 2.1 包中模块的使用:import 2.2 包的嵌套 2.3 包中模块的使用:from ...i ...

  2. Activity生命周期函数、onSaveInstanceState()和onRestoreInstanceState()的介绍

    http://www.cnblogs.com/tianzhijiexian/p/3885472.html

  3. 【windows核心编程】注入DLL时BUG排除与调试

    DLL注入排除bug的思路步骤. 1.在VS中监视输入err,hr检查DLL是否注入成功 2.OD断点loadlibraryW,loadlibraryA是否已经注入成功,eax是否有值. 3.检查路径 ...

  4. 【python图像处理】图像的缩放、旋转与翻转

    [python图像处理]图像的缩放.旋转与翻转 图像的几何变换,如缩放.旋转和翻转等,在图像处理中扮演着重要的角色,python中的Image类分别提供了这些操作的接口函数,下面进行逐一介绍. 1.图 ...

  5. ActiveMQ 入门Nodejs版

    ActiveMQ 入门下载与安装 官方下载地址 解压,运行bin/win[32|64]/activemq[.bat] 启动服务 环境信息 控制台: http://localhost:8161 默认端口 ...

  6. 【转】#ifdef __cplusplus+extern "C"的用法

    时常看到别人的头文件中,有这样的代码: #ifdef __cplusplus extern "C" { #endif //一段代码 #ifdef __cplusplus } #en ...

  7. js 、c# 编码解码

    escape不编码字符有69个:*,+,-,.,/,@,_,0-9,a-z,A-Z encodeURI不编码字符有82个:!,#,$,&,',(,),*,+,,,-,.,/,:,;,=,?,@ ...

  8. 通达OA2008从windows环境移植到linux部署手册

    通达OA2008从windows环境移植到linux中(centos5.5及以上版本) OA系统拓扑图: 环境搭建(安装lamp环境) 1.安装xampp集成lamp包xampp-linux-1.6. ...

  9. Expm 4_1 多段图中的最短路径问题

      [问题描述] 建立一个从源点S到终点T的多段图,设计一个动态规划算法求出从S到T的最短路径值,并输出相应的最短路径. 解 package org.xiu68.exp.exp4; public cl ...

  10. [Java]求文件大小并保留两位小数(文件大小是一个长整型数单位是Byte)

    前言 为了获得一堆apk的大小,并与人类友好方式显示.本来是打算用以下方法,到时不能具体到保留两位小数. org.apache.commons.io.FileUtils.byteCountToDisp ...