zoukankan      html  css  js  c++  java
  • Codeforces 818 E Card Game Again 线段树 思维

      题目链接: 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
    ", sum );
        return 0;
    }
    View Code

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

  • 相关阅读:
    Linux基础-3.用户、群组和权限
    Linux基础-2.目录文件的浏览、管理及维护
    Linux基础-1.Linux命令及获取帮助
    CentOS6.10安装详解
    有序字典
    根据公历计算农历
    常用模块
    人工智能_2_特征处理.py
    人工智能_1_初识_机器学习介绍_特征工程和文本特征提取
    python-matplotlib
  • 原文地址:https://www.cnblogs.com/FriskyPuppy/p/7381613.html
Copyright © 2011-2022 走看看