2018CCPC黑龙江省赛 - D.A Sequence Game(莫队算法/可持久化线段树)

题目大意

询问从区间最小值到区间最大值之间的每个数是否至少出现一次。


解题分析

拆成两个问题:
1.区间最大(最小)值:ST表;
2.区间种类数: 莫队算法/可持久化线段树;
若区间最大值 - 区间最小值 + 1 == 区间种类数,YES,否则为NO。


1.莫队算法 - O(n*sqrt(n))

#include
#include
#include
#include
using namespace std;
const int MAXN = 100010;

int n, m, k, block;
int a[MAXN], b[MAXN], cnt[MAXN], bel[MAXN], ans[MAXN];
int f[MAXN][20][2], cur;

struct Mo{
    int l, r, id;
    friend bool operator < (const Mo &a, const Mo &b){
        if(bel[a.l] == bel[b.l]) return a.r < b.r;
        return bel[a.l] < bel[b.l];
    }
}q[MAXN];

inline int read()
{
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9'){x = x * 10 + (c - '0'); c = getchar();}
    return x * f;
}

void st_prework()
{
    for(int i = 1; i <= n; i ++) f[i][0][0] = f[i][0][1] = a[i];
    int t = log(n) / log(2) + 1;
    for(int j = 1; j < t; j ++){
        for(int i = 1; i <= n - (1 << j) + 1; i ++){
            f[i][j][0] = min(f[i][j - 1][0], f[i + (1 << (j - 1))][j - 1][0]);
            f[i][j][1] = max(f[i][j - 1][1], f[i + (1 << (j - 1))][j - 1][1]);
        }
    }
}

int fmin(int l, int r)
{
    int k = log(r - l + 1)/log(2);
    return min(f[l][k][0], f[r - (1 << k) + 1][k][0]);
}

int fmax(int l, int r)
{
    int k = log(r - l + 1)/log(2);
    return max(f[l][k][1], f[r - (1 << k) + 1][k][1]);
}

void add(int x)
{
    if(++ cnt[x] == 1) cur ++;
}

void del(int x)
{
    if(-- cnt[x] == 0) cur --;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    int T = read();
    while(T --){
        n = read(); block = sqrt(n);
        m = read();
        for(int i = 1; i <= n; i ++){
            a[i] = b[i] = read();
            bel[i] = (i - 1)/block + 1;
        }
        st_prework();
        sort(b + 1, b + n + 1);
        k = unique(b + 1, b + n + 1) - b - 1;
        for(int i = 1; i <= n; i ++) a[i] = lower_bound(b + 1, b + k + 1, a[i]) - b;
        for(int i = 1; i <= m; i ++){
            q[i].l = read(); q[i].r = read();
            q[i].id = i;
        }
        sort(q + 1, q + m + 1);
        int l = 1, r = 0;
        cur = 0;
        memset(cnt, 0, sizeof(cnt));
        for(int i = 1; i <= m; i ++){
            int mi = fmin(q[i].l, q[i].r);
            int ma = fmax(q[i].l, q[i].r);
            while(l < q[i].l) del(a[l ++]);
            while(l > q[i].l) add(a[-- l]);
            while(r < q[i].r) add(a[++ r]);
            while(r > q[i].r) del(a[r --]);
            ans[q[i].id] = ma - mi + 1 == cur ? 1 : 0;
        }
        for(int i = 1; i <= m; i ++)
            puts(ans[i] ? "YES" : "NO");
    }
    return 0;
}

2.可持久化线段树 - O(n*log(n))

对每个位置建一棵线段树, rt[i] r t [ i ] 记录截止至第 i i 个位置, [1,n] [ 1 , n ] 上不重复数字的个数。从左往右建树:若未出现过 a[i] a [ i ] ,直接将 a[i] a [ i ] 插入到 rt[i] r t [ i ] i i 位置;若出现过 a[i] a [ i ] ,先删除 rt[i1] r t [ i − 1 ] 中对应位置的 a[i] a [ i ] ,再把 a[i] a [ i ] 插入到 rt[i] r t [ i ] i i 位置。求区间种类数时,只需询问 rt[r] r t [ r ] 树中 [l,r] [ l , r ] 区间之和即可。

#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 100010;

inline int read()
{
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9'){if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9'){x = x * 10 + (c - '0'); c = getchar();}
    return x * f;
}

int n, m, tmp;
int a[MAXN], f[MAXN][20][2];
map<int, int>pos;

void st_prework()
{
    for(int i = 1; i <= n; i ++) f[i][0][0] = f[i][0][1] = a[i];
    int t = log(n) / log(2) + 1;
    for(int j = 1; j < t; j ++){
        for(int i = 1; i <= n - (1 << j) + 1; i ++){
            f[i][j][0] = min(f[i][j - 1][0], f[i + (1 << (j - 1))][j - 1][0]);
            f[i][j][1] = max(f[i][j - 1][1], f[i + (1 << (j - 1))][j - 1][1]);
        }
    }
}

int fmin(int l, int r)
{
    int k = log(r - l + 1)/log(2);
    return min(f[l][k][0], f[r - (1 << k) + 1][k][0]);
}

int fmax(int l, int r)
{
    int k = log(r - l + 1)/log(2);
    return max(f[l][k][1], f[r - (1 << k) + 1][k][1]);
}

struct PresidentTree{int lc, rc, sum;}pt[MAXN * 22];
int num, rt[MAXN];
#define mid ((l + r) >> 1)
#define lc(x) pt[x].lc
#define rc(x) pt[x].rc
#define sum(x) pt[x].sum

void build(int &k1, int l, int r)
{
    k1 = ++ num; sum(k1) = 0;
    if(l == r) return;
    build(lc(k1), l, mid);
    build(rc(k1), mid + 1, r);
}

void modify(int &k1, int pre, int l, int r, int p, int v)
{
    k1 = ++ num; pt[k1] = pt[pre]; sum(k1) = sum(pre) + v;
    if(l == r) return;
    if(p <= mid) modify(lc(k1), lc(pre), l, mid, p, v);
    else modify(rc(k1), rc(pre), mid + 1, r, p, v);
}

int query(int k1, int l, int r, int ql, int qr)
{
    if(ql <= l && r <= qr) return sum(k1);
    int ans = 0;
    if(ql <= mid) ans += query(lc(k1), l, mid, ql, qr);
    if(qr > mid) ans += query(rc(k1), mid + 1, r, ql, qr);
    return ans;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    int T = read();
    while(T --){
        n = read(), m = read();
        for(int i = 1; i <= n; i ++) a[i] = read();
        st_prework();
        num = 0; pos.clear();
        build(rt[0], 1, n);
        for(int i = 1; i <= n; i ++){
            if(pos[a[i]]){
                modify(tmp, rt[i - 1], 1, n, pos[a[i]], -1);
                modify(rt[i], tmp, 1, n, i, 1);
            }
            else{
                modify(rt[i], rt[i - 1], 1, n, i, 1);
            }
            pos[a[i]] = i;
        }
        while(m --){
            int l = read(), r = read();
            int mi = fmin(l, r);
            int ma = fmax(l, r);
            tmp = query(rt[r], 1, n, l, r);
            puts(tmp == ma - mi + 1 ? "YES" : "NO");
        }
    }
    return 0;
}

你可能感兴趣的:(ACM算法)