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

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

原文地址:https://www.cnblogs.com/FriskyPuppy/p/7381613.html