Codeforces 818 E Card Game Again 线段树 思维

本文详细解析了CodeForces 818E题目,介绍了一种利用线段树优化求解区间乘积能否整除特定数值的问题。通过二分查找和线段树维护区间状态的方法,实现了O(nlogn)的时间复杂度。

  题目链接: http://codeforces.com/problemset/problem/818/E

  题目描述: 给你N个数, 给你一个M, 问有多少个连续区间的乘积能整除M

  解题思路: 我首先想的数处理前缀和, 但是这样就只能枚举两端点, O(n^2)肯定是T的, 这题看得题解.......题解用的是线段树, 存的是每一个区间的乘积, 每次查询查询的是当前区间的乘积, 需要得到的是离当前起点最近的连乘等于0的位置, 实际上我们在得到位置是二分得到的,我们枚举起点, 然后二分,  所以说复杂度是O(nlogn), 如果找到那个下标, 那么后面的所有数肯定都可以, 所以sum加一下就可以了。

  代码: 

#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
#include <iterator>
#include <cmath>
#include <algorithm>
#include <stack>
#include <deque>
#include <map>
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
#define mem0(a) memset(a,0,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))
typedef long long ll;
using namespace std;

//const int INF = 0x3fffffff;
const int maxn = 1e5 + 100;
ll pro[maxn<<2];
ll arr[maxn];
int n, k;

void pushup( int rt ) {
    pro[rt] = ( pro[rt<<1] % k * pro[rt<<1|1] % k ) % k;
}

void build( int l, int r, int rt ) {
    if( l == r ) {
        pro[rt] = arr[l] % k;
        return;
    }
    int m = (l + r) >> 1;
    build( lson );
    build( rson );
    pushup( rt );
}

ll query( int L, int R, int l, int r, int rt ) {
    if( L <= l && r <= R ) {
        return pro[rt];
    }
    int m = (l + r) >> 1;
    ll ret = 1;
    if( L <= m ) ret *= query(L, R, lson ) % k;
    if( R > m ) ret *= query(L, R, rson ) % k;
    return ret % k;
}

int main() {
    mem0( arr );
    mem0( pro );
    scanf( "%d%d", &n, &k );
    for( int i = 1; i <= n; i++ ) {
        scanf( "%lld", arr+i );
    }
    build( 1, n, 1 );
//    for( int i = 1; i <= 10; i++ ) {
//        cout << pro[i] << " ";
//    }
//    cout << endl;
    ll sum = 0;
    for( int i = 1; i <= n; i++ ) {
        int l = i;
        int r = n;
        int pos = n;
        while( l < r ) { // 二分找到最左面的连乘是k的倍数的数
            int mid = (l + r) >> 1;
            if( query(i, mid, 1, n, 1) % k == 0 ) {
                pos = mid;
                r = mid;
            }
            else {
                l = mid+1;
            }
        }
        if( query(i, pos, 1, n, 1) % k == 0 ) { // 有可能pos = n但是不可以整除, 这样判断一下就排除了这种情况
//            cout << pos << endl;
            sum += n-pos+1; // 如果当前位可以整除 k 则乘上后面的数就都可以了, 所以方案数要加上后面的个数
        }
    }
    printf( "%lld\n", sum );
    return 0;
}
View Code

  思考: 其实自己也想过线段树来做, 只是线段的用处没有想好.....没有想到通过找那个下标来二分来减少复杂度, 自己做的题还是太少了

 

转载于:https://www.cnblogs.com/FriskyPuppy/p/7381613.html

### 关于C++实现线段树思维导图 线段树是一种高效的数据结构,主要用于解决区间查询和更新问题。以下是围绕C++中实现线段树的相关知识点构建的一个思维导图框架: #### 1. **线段树的基础概念** - 定义:线段树是一种二叉树结构,用于处理数组上的范围查询和修改操作[^3]。 - 特点: - 每个节点表示一个区间。 - 叶子节点对应数组中的单个元素。 - 非叶子节点表示其两个子区间的并集。 #### 2. **线段树的核心功能** - 区间求和/最大值/最小值:通过递归方式计算指定区间的值。 - 单点更新:更改某个位置的值,并相应调整受影响的父节点。 - 区间更新:批量修改某一段区域内的值。 #### 3. **C++实现的关键要素** - 构造函数初始化列表:在线段树类定义中,利用构造函数初始化列表完成必要的变量设置[^5]。 - 动态内存分配:通常使用`std::vector<int>`作为底层容器存储节点数据。 - 延迟标记(Lazy Propagation):优化大规模区间更新操作,减少冗余计算。 ```cpp #include <iostream> #include <vector> using namespace std; class SegmentTree { private: vector<int> tree; int n; void build(int node, int start, int end, const vector<int>& arr) { if (start == end) { tree[node] = arr[start]; } else { int mid = (start + end) / 2; build(2 * node, start, mid, arr); build(2 * node + 1, mid + 1, end, arr); tree[node] = tree[2 * node] + tree[2 * node + 1]; // 修改此处可支持其他运算 } } public: SegmentTree(const vector<int>& arr) : n(arr.size()), tree(4 * n, 0) { build(1, 0, n - 1, arr); // 根节点编号为1 } int query(int node, int start, int end, int l, int r) { if (r < start || l > end) return 0; // 不相交的情况 if (l <= start && end <= r) return tree[node]; // 完全包含 int mid = (start + end) / 2; return query(2 * node, start, mid, l, r) + query(2 * node + 1, mid + 1, end, l, r); } void update(int node, int start, int end, int idx, int val) { if (start == end) { tree[node] += val; } else { int mid = (start + end) / 2; if (idx <= mid) { update(2 * node, start, mid, idx, val); } else { update(2 * node + 1, mid + 1, end, idx, val); } tree[node] = tree[2 * node] + tree[2 * node + 1]; } } }; ``` #### 4. **学习资源推荐** - 推荐书籍:《算法竞赛入门经典——训练指南》,其中详细讲解了线段树的应用场景及其变种形式[^2]。 - 在线教程:LeetCode 和 Codeforces 平台提供了丰富的练习题目以及社区讨论[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值