[luogu4256] 公主の#19准备月考(线段树,状压)

公主面临月考挑战,特别是在文综方面。通过线段树算法帮助公主优化选择题得分,涉及预估值的gcd、lcm计算及质因数分解等操作。

题目

背景

公主在玩完游戏后,也要月考了。(就算是公主也要月考啊QWQ)

描述

公主的文综太差了,全校排名1100+(全校就1100多人),她分析了好久,发现她如果把所有时间放在选择题上,得分会比较好一点。
文综题目共有n个,编号从1到n
公主给每个题目算出来了一个预估值Ai,她认为,一段连续题目的答案会在它们的预估值的gcd和lcm之间;有时候她的想法不同了,一些题目的预估值会改变;有时候,会出现多选题,多选题的答案数量就是一段连续题目答案的预估值的公约数的个数。
具体来说,对于一个数列,有四种操作:
L x y p 表示公主询问区间[x,y]的数字的lcm对p取模之后的值
G x y p 表示公主询问区间[x,y]的数字的gcd对p取模之后的值
C x y c 表示公主改变区间[x,y]的数字的值,统一为c
S x y p 表示公主询问区间[x,y]的数字的公因数个数对p取模之后的值
公主月考不能挂科,不然她就不能学习OI了(假的),所以请你帮帮她吧!

输入

第一行,两个正整数n和q,q表示操作次数
第二行,n个正整数,表示dkw对题目的预估值
接下来q行,每行输入一个操作,格式详见题目描述

输出

对于每个询问,输出它的答案。

输入样例

10 10
42 68 35 1 70 25 79 59 63 65 
L 2 6 28
L 2 6 43
G 2 7 5
G 3 4 83
L 7 9 96
G 2 7 39
S 3 8 100
L 4 5 12
G 4 4 65
L 2 4 69

输出样例

0
32
1
1
75
1
1
10
1
34

说明

对于30%的数据,1<=n,q<=1000
对于另外20%的数据,1<=n<=1000,1<=q<=100000
对于另外20%的数据,1<=n<=100000,1<=q<=100000,保证没有修改操作
对于100%的数据,1<=n<=300000,1<=q<=300000
保证任何时刻每个题目的预估值都在[1,100]之间,答案取模之后不超过int


解题思路

肯定是线段树啊!
但是,这道题每次询问时的模数不同!所以直接存gcd就算了吧。
不过数据保证所有值都在[1,100][1,100]之间,而100以内的质数很少,更重要的是,gcd和lcm都是质因数相关的值,所以我们考虑存一下每个值质因数分解后的情况——于是,gcdgcd 就是每个质因数次数取 minmin 再相乘,求 lcmlcm 就是每个质因数次数取 maxmax 再相乘,这样就可以很轻松的用线段树维护了。

然而,如果每一个线段树节点都开一个数组来记录质因数分解的结果,不仅时间多了个 3535 的常数,空间也多了个 3535——MLEMLE
怎么办呢?发现所有[1,100][1,100]的数进行质因数分解后只有35个因数(6个2,4个3,2个5,2个7,其余100以内质数各一个),那么我们可以用一个long long状压,并且状压后线段树的维护更简单——求gcd就将两状态相与,求lcm就将两状态相或。

时间复杂度O(nlogn)O(nlogn)


Code

#include<cstdio>

#define lid id<<1
#define rid id<<1|1
#define mid ((tr[id].l+tr[id].r)>>1)

using namespace std;

typedef long long LL;

const int N = 300005;
int n, q, a[N], ql, qr, qp, cnt[105];
char opt[2];
int list[40] = {2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 5, 5, 7, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};

struct segTree{
    int l, r, cov;
    LL mx, mn;
}tr[N<<2];

inline LL getBit(int x){
    LL ans = 0;
    for(int i = 0; i < 35; i++)
        if(x % list[i] == 0){
            ans |= (1ll << i);
            x /= list[i];
        }
    return ans;
}

inline void pushup(int id){
    tr[id].mx = tr[lid].mx | tr[rid].mx;
    tr[id].mn = tr[lid].mn & tr[rid].mn;
}

inline void pushdown(int id){
    if(tr[id].l == tr[id].r)    return;
    if(tr[id].cov){
        tr[lid].cov = tr[rid].cov = tr[id].cov;
        tr[lid].mx = tr[lid].mn = tr[rid].mx = tr[rid].mn = getBit(tr[id].cov);
        tr[id].cov = 0;
    }
}

void build(int id, int l, int r){
    tr[id].l = l, tr[id].r = r;
    if(tr[id].l == tr[id].r){
        tr[id].mx = tr[id].mn = getBit(a[l]);
        return;
    }
    build(lid, l, mid);
    build(rid, mid+1, r);
    pushup(id);
}

void modify(int id, int l, int r, int val){
    pushdown(id);
    if(tr[id].l == l && tr[id].r == r){
        tr[id].cov = val;
        tr[id].mx = tr[id].mn = getBit(val);
        return;
    }
    if(r <= mid)    modify(lid, l, r, val);
    else if(l > mid)    modify(rid, l, r, val);
    else    modify(lid, l, mid, val), modify(rid, mid+1, r, val);
    pushup(id);
}

segTree query(int id, int l, int r){
    pushdown(id);
    if(tr[id].l == l && tr[id].r == r)  return tr[id];
    if(r <= mid)    return query(lid, l, r);
    else if(l > mid)    return query(rid, l, r);
    else{
        segTree t, t1 = query(lid, l, mid), t2 = query(rid, mid+1, r);
        t.mx = t1.mx | t2.mx;
        t.mn = t1.mn & t2.mn;
        return t;
    }
}

int main(){
    for(int i = 1; i <= 100; i++)
        for(int j = 1; j * j <= i; j++)
            if(i % j == 0){
                cnt[i] += 2;
                if(j * j == i)  cnt[i]--;
            }
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    while(q--){
        scanf("%s%d%d%d", opt, &ql, &qr, &qp);
        if(opt[0] == 'C')   modify(1, ql, qr, qp);
        else{
            segTree t = query(1, ql, qr);
            LL ans = 1ll;
            if(opt[0] == 'L'){
                for(int i = 0; i < 35; i++)
                    if((t.mx >> i) & 1)
                        (ans *= list[i]) %= qp;
                printf("%lld\n", ans % qp);
            }
            else{
                for(int i = 0; i < 35; i++)
                    if((t.mn >> i) & 1)
                        ans *= list[i];
                if(opt[0] == 'G')   printf("%lld\n", ans % qp);
                else if(opt[0] == 'S')  printf("%d\n", cnt[ans] % qp);
            }
        }
    }
    return 0;
}
### 树的直径问题概述 树的直径是指树中最长的简单路径,通常定义为两个节点之间的最大距离。解决树的直径问题的方法主要包括动态规划和贪心算法两种思路。 #### 动态规划方法 动态规划方法中,可以通过两次深度优先搜索(DFS)来求解树的直径。具体步骤如下: 1. 从任意一个节点出发进行一次 DFS,找到距离该节点最远的节点 $ u $。 2. 从节点 $ u $ 再次进行一次 DFS,找到距离 $ u $ 最远的节点 $ v $,路径 $ u \rightarrow v $ 即为树的直径。 在实现过程中,可以维护两个数组 `dp` 和 `dp2`,分别表示从某个节点出发的最长路径和次长路径。通过更新这两个数组,可以计算出经过每个节点的最长路径,并最终找到整个树的最长路径。 ```cpp void dfs(int u, int fa) { for (auto x : g[u]) { if (x == fa) continue; dfs(x, u); f[u] = max(f[u], d[u] + d[x] + 1); d[u] = max(d[u], d[x] + 1); } } ``` #### 贪心方法 贪心方法的核心思想是通过两次 DFS 找到树的直径。第一次 DFS 用于找到距离任意起点最远的节点 $ u $,第二次 DFS 则从 $ u $ 出发找到最远的节点 $ v $。路径 $ u \rightarrow v $ 即为树的最长路径。 这种方法的时间复杂度为 $ O(n) $,适用于大多数树的直径问题。 #### 洛谷 P1099 树网的核问题 在洛谷 P1099 [NOIP2007 提高组] 树网的核问题中,树的直径是核心概念之一。题目要求找到树中的一条路径,使得该路径的长度不超过给定值,并且尽可能多地覆盖树中的节点。树的直径在该问题中起到了关键作用,通常需要结合枚举和树的直径特性进行求解。 #### 树的最长路径算法 树的最长路径算法通常包括以下步骤: 1. **选择起点**:从任意一个节点开始进行 DFS。 2. **寻找最远节点**:通过 DFS 找到距离起点最远的节点 $ u $。 3. **再次寻找最远节点**:从 $ u $ 开始进行第二次 DFS,找到距离 $ u $ 最远的节点 $ v $。 4. **计算直径**:路径 $ u \rightarrow v $ 即为树的直径。 该算法的时间复杂度为 $ O(n) $,适用于大多数树的直径问题。 ### 相关问题 1. 如何通过两次 DFS 找到树的直径? 2. 树的直径问题中的动态规划方法是如何实现的? 3. 洛谷 P1099 树网的核问题中如何应用树的直径特性? 4. 树的最长路径算法的时间复杂度是多少? 5. 如何通过贪心算法解决树的直径问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值