zoukankan      html  css  js  c++  java
  • CF 1288 E. Messenger Simulator

    CF 1288 E. Messenger Simulator

    题目传送门

    官方题解

    题意想必大家都明白了这里就不赘述了,这里只想重点记录一下几种实现方法

    分析

    设向前移动的序列为(a)序列

    1. 对于没有向前移动的数字,出现的最小位置就是它最开始的位置,而求解最大位置时,只需要求出a序列中,有多少种比它大即可(注意不要计算重复)
    2. 对于向前移动了的数字 (i),出现的最小位置是1。求解最大位置:将它在a序列中出现的位置存放在vector中,对于 i 出现的第一个位置(pos), 计算([1,pos-1])中有多少种比 i 大的数字出现。对于 i 出现的第 j 个位置(pos_j), 计算([pos_{j-1}+1,pos_{j}-1]) 中有多少种数字出现。

    所以问题简化为:

    • 求前缀序列中大于某个数字的数字种类数

      • 树状数组维护显然可做
      • 权值线段树
    • 求数字种类数

      • 将查询排序,莫队(离线)复杂度(O(nsqrt{n}))

      • 主席树(在线)时间复杂度(O(nlogn))

      • 将查询排序,从左到右枚举右端点 (r)(即查询的右端点),线段树中维护区间[L,R]中,有多少数字是[1,r]中最后一次出现。

        • [1,2,1,2,3,5],当 (r=6)时,线段树中每个位置的值为[0,0,1,1,1,1]
        • [1,2,3,2,4], 当(r=4)时,线段树中每个位置的值为[1,0,1,1,0]

        然后直接在线段树上查询即可,复杂度(O(nlogn))

    除了数据结构的做法, 还有模拟做法,虽然无法很方便的用数组来模拟每个数字向前移动的情况,但是我们可以很方便的用数组来模拟每个数字添加到尾部的操作,由于我们在计算中只需要利用的是这些数字排列顺序,所以可以用树状数组来维护每个位置的情况。初始时按照n到1的顺序放在树状数组上,对于第 i 个移动的数字 a[i], pos[a[i]]表示它现在在树状数组的位置,进行如下操作

    1. 查询树状数组[1, pos[a[i]]-1], 记结果为 x ,则用 n - x 更新a[i]的答案
    2. 树状数组中 pos[a[i]] 上的值减1
    3. pos[a[i]] = n + i ,该等效于移动到队尾
    4. 树状数组中 pos[a[i]] 上的值加1

    下面给出四种代码

    1. 树状数组 + 主席树
    const int N = 300000 + 5;
    int a[N], n, m, c[N];
    vector<int> v[N];
    struct SegTree{
        int lc, rc;
        int dat;
    } t[N * 60];
    int tot, root[N], mi[N], mx[N], past[N];
    int build(int l,int r){
        int p = ++tot;
        if(l == r){
            t[p].dat = 0;
            return p;
        }
        int mid = l + r >> 1;
        t[p].lc = build(l, mid);
        t[p].rc = build(mid + 1, r);
        t[p].dat = 0;
        return p;
    }
    int insert(int now,int l,int r,int x,int val){ // l,r为当前线段树节点的左右区间
        int p = ++tot;
        t[p] = t[now];
        if(l == r ){
            t[p].dat += val;
            return p;
        }
        int mid = l + r >> 1;
        if(x <= mid)
            t[p].lc = insert(t[now].lc, l, mid, x, val);
        else
            t[p].rc = insert(t[now].rc, mid + 1, r, x, val);
        t[p].dat = t[t[p].lc].dat + t[t[p].rc].dat;
        return p;
    }
    int query(int p,int L,int R,int l,int r){
        if(L >= l && R <= r){
            return t[p].dat;
        }
        int mid = L + R >> 1;
        int res = 0;
        if(mid >= l)
            res += query(t[p].lc, L, mid, l, r);
        if(mid < r)
            res += query(t[p].rc, mid + 1, R, l, r);
        return res;
    }
    void upd(int x){
        for (; x <= n;x+=x&-x)
            c[x] += 1;
    }
    int ask(int x){
        int res = 0;
        for (; x;x-=x&-x)
            res += c[x];
        return res;
    }
    int main() {
        scanf("%d%d", &n, &m);
        int maxn = n;
        root[0] = build(1, maxn);
        for (int i = 1; i <= m;i++){
            scanf("%d", &a[i]);
            v[a[i]].push_back(i);
            if(past[a[i]]){ //past[a[i]] 为a[i] 上次出现的位置
                root[i] = insert(root[i - 1], 1, m, past[a[i]], -1); //减1操作在旧位置减1
                root[i] = insert(root[i], 1, m, i, 1);//在新位置上+1
            }else{
                root[i] = insert(root[i - 1], 1, m, i, 1);
            }
            past[a[i]] = i;
        }
        root[m + 1] = root[m];
        for (int i = 1; i <= n;i++){
            mi[i] = mx[i] = i;
        }
        for (int i = 1; i <= m;i++){
            mi[a[i]] = 1;
            if(i == v[a[i]][0]){//i是a[i]第一次出现的位置
                mx[a[i]] += ask(n) - ask(a[i]);//树状数组查询[1,i-1]中有多少种数字大于a[i]
                upd(a[i]);
            }
        }
        for (int i = 1; i <= n;i++){
            if(v[i].size()==0)//求解[1,m]中有多少数字大于 i
                mx[i] += ask(n) - ask(i);
        }
        for (int i = 1; i <= maxn; i++) {
            if (v[i].size()) {
                v[i].push_back(m + 1);
                for (int j = 1; j < v[i].size(); j++) {
                    mx[i] = max(mx[i], query(root[v[i][j] - 1], 1, m, v[i][j - 1] + 1, v[i][j] - 1) + 1);//主席树在线查询[v[i][j-1]+1, v[i][j]-1]有多少种数字
                }
            }
        }
        for (int i = 1; i <= n;i++){
            printf("%d %d
    ", mi[i], mx[i]);
        }
        return 0;
    }
    
    1. 树状数组+莫队
    const int N = 300000 + 5;
    
    #define x first
    #define y second
    typedef pair<int, int> pii;
    int n, m, a[N], c[N];
    vector<int> v[N];
    vector<pii> qr;
    pii res[N];
    int cnt[N], tot;
    const int T = 550;
    void upd (int x){
        for (; x <= n;x+=x&-x)
            c[x] += 1;
    }
    int ask(int x){
        int res = 0;
        for (; x;x-=x&-x){
            res += c[x];
        }
        return res;
    }
    void add(int x){
        if(++cnt[x] == 1)
            tot++;
    }
    void rem(int x){
        if(--cnt[x] == 0)
            tot--;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n;i++){
            res[i].x = res[i].y = i;
        }
        for (int i = 1; i <= m; i++) {
            scanf("%d", &a[i]);
            res[a[i]].x = 1;
            v[a[i]].push_back(i);
        }
        for (int i = 1; i <= m;i++){
            if(i == v[a[i]][0]){
                res[a[i]].y += ask(n) - ask(a[i]); // [a[i]+1,n]区间出现的数字个数
                upd(a[i]);
            }
        }
        for (int i = 1; i <= n;i++){
            if(v[i].size() == 0)
                res[i].y += ask(n) - ask(i);//[i+1,n] 区间出现的个数
            else{
                v[i].push_back(m + 1);
                for (int j = 1; j < v[i].size();j++){
                    qr.push_back(make_pair(v[i][j - 1] + 1, v[i][j] - 1));
                }
            }
        }
        //区间排序,左端点分块
        sort(qr.begin(), qr.end(), [](const pii a, const pii b) {
            if(a.x / T != b.x / T){
                return a.x / T < b.x / T;
            }
            if((a.x / T) & 1) return a.y < b.y;//左端点属于奇数块,则右端点按照升序排列,否则按照降序排列,是莫队算法的一种优化
            return a.y > b.y;
        });
        int L = 1, R = 0;
        for (int i = 0;i<qr.size();i++){
            int l = qr[i].x, r = qr[i].y;
            if(r < l)
                continue;
            while(L < l)
                rem(a[L++]);
            while(L > l)
                add(a[--L]);
            while(R > r)
                rem(a[R--]);
            while(R < r)
                add(a[++R]);
            int x = a[qr[i].x - 1];
            res[x].y = max(res[x].y, tot + 1);
        }
        for (int i = 1; i <= n;i++){
            printf("%d %d
    ", res[i].x, res[i].y);
        }
        return 0;
    }
    
    1. 树状数组+线段树
    const int N = 300000 + 5;
    
    #define x first
    #define y second
    typedef pair<int, int> pii;
    int n, m, a[N], c[N];
    int past[N];
    vector<int> v[N];
    vector<int> q[N];
    pii res[N];
    struct SegTree{
        int l, r, dat;
    } t[N * 4];
    void build(int p,int l,int r){
        t[p].l = l, t[p].r = r;
        if(l == r){
            t[p].dat = 0;
            return;
        }
        int mid = l + r >> 1;
        build(p * 2, l, mid);
        build(p * 2 + 1, mid + 1, r);
        t[p].dat = 0;
    }
    void upd(int p,int x,int val){
        if(t[p].l == t[p].r && t[p].l == x){
            t[p].dat += val;
            return;
        }
        int mid = t[p].l + t[p].r >> 1;
        if(x <= mid)
            upd(p * 2, x, val);
        if(x > mid)
            upd(p * 2 + 1, x, val);
        t[p].dat = t[p * 2].dat + t[p * 2 + 1].dat;
    }
    int ask(int p,int l,int r){
        if(t[p].l >= l && t[p].r <= r)
            return t[p].dat;
        int res = 0;
        int mid = t[p].l + t[p].r >> 1;
        if(mid >= l)
            res += ask(p * 2, l, r);
        if(mid < r)
            res += ask(p * 2 + 1, l, r);
        return res;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n;i++){
            res[i].x = res[i].y = i;
        }
        build(1, 1, n);//值域为[1,n]的权值线段树
        for (int i = 1; i <= m; i++) {
            scanf("%d", &a[i]);
            res[a[i]].x = 1;
            v[a[i]].push_back(i);
        }
        for (int i = 1; i <= m;i++){
            if(i == v[a[i]][0]){
                res[a[i]].y += ask(1, a[i] + 1, n);//权值线段树查询[a[i]+1,n]的种类数
                upd(1, a[i], 1);
            }
        }
        for (int i = 1; i <= n; i++) {
            if (v[i].size()) {
                v[i].push_back(m + 1);
                for (int j = 1; j < v[i].size(); j++) {
                    q[v[i][j] - 1].push_back(v[i][j - 1] + 1);//离线保存查询
                }
            }else{
                res[i].y += ask(1, i + 1, n);
            }
        }
        build(1, 1, m);//重新构建普通线段树
        for (int i = 1; i <= m;i++){
            if(past[a[i]]){//past[a[i]]为a[i]上一次出现的位置
                upd(1, past[a[i]], -1);
                upd(1, i, 1);
            }
            else
                upd(1, i, 1);
            for (int j = 0; j < q[i].size();j++){
                int l = q[i][j], r = i;
                int x = a[l - 1];
                res[x].y = max(res[x].y, ask(1, l, r) + 1);
            }
            past[a[i]] = i;
        }
        for (int i = 1; i <= n; i++) {
            printf("%d %d
    ", res[i].x, res[i].y);
        }
        return 0;
    }
    
    1. 树状数组+倒序模拟
    const int N = 300000 + 5;
    typedef pair<int, int> pii;
    int n, m, a[N], pos[N], c[N + N];
    pii res[N];
    void upd(int x,int val){
        for (; x <= n + m;x+=x&-x)
            c[x] += val;
    }
    int ask(int x){
        int res = 0;
        for (; x; x-=x&-x)
            res += c[x];
        return res;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m;i++)
            scanf("%d", &a[i]);
    
        for (int i = 1; i <= n;i++){
            res[i].first = res[i].second = i;
            upd(n - i + 1, 1);
            pos[i] = n - i + 1;
        }
        for (int i = 1; i <= m;i++) {
            res[a[i]].first = 1;
            res[a[i]].second = max(res[a[i]].second, n - ask(pos[a[i]] - 1));
            upd(pos[a[i]], -1);
            pos[a[i]] = i + n;
            upd(pos[a[i]], 1);
        }
        for (int i = 1; i <= n;i++){
            res[i].second = max(res[i].second, n - ask(pos[i] - 1));
            printf("%d %d
    ", res[i].first, res[i].second);
        }
        return 0;
    }
    
  • 相关阅读:
    golang_并发安全: slice和map并发不安全及解决方法
    什么情况下需要用到互斥锁sync.Mutex?
    使用Charles进行HTTPS抓包
    centos6 yum 源失效 404,终于解决了
    GOMAXPROCS你设置对了吗?
    容器资源可见性问题与 GOMAXPROCS 配置
    gflags 使用方式
    分布式训练问题
    NCCL常用环境变量
    git 常用命令
  • 原文地址:https://www.cnblogs.com/1625--H/p/12290624.html
Copyright © 2011-2022 走看看