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. jquery选择器最后一个,倒数第二个元素

    <div> <p>1</p> <p>2</p> <p>3</p> <p>4</p> < ...

  2. linux 下程序员专用搜索源码用来替代grep的软件ack(后来发现一个更快的: ag), 且有vim插件的

    发现一个比ack更快更好用的:  https://github.com/ggreer/the_silver_searcher   , 使用时命令为ag,它是基于ack的代码二次开发的,所有使用方法基本 ...

  3. WordCloud词云包的安装

    1,下载 https://www.lfd.uci.edu/~gohlke/pythonlibs/#wordcloud 2,安装 (window环境安装) 找的下载文件的路径 安装 pip instal ...

  4. CentOS 6.5环境下使用HAProxy+apache实现web服务的动静分离

    HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案.HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持 ...

  5. centos7.2环境编译安装mysql5.5.48

    一.安装cmake编译工具 跨平台编译器 查看是否已经安装了gcc # rpm -qa | grep gcc # yum install -y gcc-c++ # yum install -y cma ...

  6. Android网络通信(8):WiFi Direct

    Android网络通信之WiFi Direct 使用Wi-Fi Direct技术可以让具备硬件支持的设备在没有中间接入点的情况下进行直接互联.Android 4.0(API版本14)及以后的系统都提供 ...

  7. vue系列之MVVM框架

    当数据发生变化时,ViewModel就会检测到,然后通知相应的View改变 当用户操作View时,ViewModel就会检测到,然后Model,修改相应的数据,最终实现双向绑定 适用场景:针对具有复杂 ...

  8. vue-cli之webpack的proxyTable无效的解决方案

    最近遇到这个需要单页访问跨域后台的问题 可以按照如下设置: proxyTable: { '/list': { target: 'http://api.xxxxxxxx.com', pathRewrit ...

  9. 使用@font-family时各浏览器对字体格式(format)的支持情况

    说到浏览器对@font-face的兼容问题,这里涉及到一个字体format的问题,因为不同的浏览器对字体格式支持是不一致的,这样大家有必要了解一下,各种版本的浏览器支持什么样的字体,前面也简单带到了有 ...

  10. Laravel Form 表单的数据校验

    例如,要使用手机号加验证码的方式提供登录网站的功能,那么在处理前端提交的 form 表单时,就不得不对提交的手机号及验证码做基本的数据校验. 手写规则,非常浪费时间.使用 laravel 内置的 va ...