zoukankan      html  css  js  c++  java
  • hihocoder-第六十一周 Combination Lock

    时间限制:10000ms
    单点时限:1000ms
    内存限制:256MB

    描述

    Finally, you come to the interview room. You know that a Microsoft interviewer is in the room though the door is locked. There is a combination lock on the door. There are N rotators on the lock, each consists of 26 alphabetic characters, namely, 'A'-'Z'. You need to unlock the door to meet the interviewer inside. There is a note besides the lock, which shows the steps to unlock it.

    Note: There are M steps totally; each step is one of the four kinds of operations shown below:

    Type1: CMD 1 i j X: (i and j are integers, 1 <= i <= j <= N; X is a character, within 'A'-'Z')

    This is a sequence operation: turn the ith to the jth rotators to character X (the left most rotator is defined as the 1st rotator)

    For example: ABCDEFG => CMD 1 2 3 Z => AZZDEFG

    Type2: CMD 2 i j K: (i, j, and K are all integers, 1 <= i <= j <= N)

    This is a sequence operation: turn the ith to the jth rotators up K times ( if character A is turned up once, it is B; if Z is turned up once, it is A now. )

    For example: ABCDEFG => CMD 2 2 3 1 => ACDDEFG

    Type3: CMD 3 K: (K is an integer, 1 <= K <= N)

    This is a concatenation operation: move the K leftmost rotators to the rightmost end.

    For example: ABCDEFG => CMD 3 3 => DEFGABC

    Type4: CMD 4 i j(i, j are integers, 1 <= i <= j <= N):

    This is a recursive operation, which means:

    If i > j:
    	Do Nothing
    Else:
    	CMD 4 i+1 j
    	CMD 2 i j 1

    For example: ABCDEFG => CMD 4 2 3 => ACEDEFG

    输入

    1st line:  2 integers, N, M ( 1 <= N <= 50000, 1 <= M <= 50000 )

    2nd line: a string of N characters, standing for the original status of the lock.

    3rd ~ (3+M-1)th lines: each line contains a string, representing one step.

    输出

    One line of N characters, showing the final status of the lock.

    提示

    Come on! You need to do these operations as fast as possible.

    样例输入
    7 4
    ABCDEFG
    CMD 1 2 5 C
    CMD 2 3 7 4
    CMD 3 3
    CMD 4 1 7
    样例输出
    HIMOFIN

    题目分析:

    题意分析

    给定一个字符串s,以及对该字符串s的 m 个操作。

    字符串s包含n个字符,下标为1..n。字符由'A'到'Z'构成,字符增加1表示该字符变为后续字符,比如'A'增加1是'B''C'增加1是'D'。需要注意的是'Z'增加1是'A'

    m个操作包含以下四种类型:

    1. 将字符串第i位到第j位设定为C

      比如当i=2,j=3,C='Z'时:"ABCDEFG"变成"AZZDEFG"

    2. 将字符串第i位到第j位增加K

      比如i=2,j=3,K=1时:"ABCDEFG"变成"ACDDEFG"

    3. 将字符串左边K位移至右边。

      比如K=3时:"ABCDEFG"变成"DEFGABC"

    4. 从字符串第i位到第j位,依次增加1,2,...,j-i+1。

      比如当i=2,j=3时:"ABCDEFG"变成"ACEDEFG"

    输出m个操作结束后的字符串s

    算法分析

    本题需要根据每一次的操作去修改现在的s。若采用朴素的做法,每一次修改其最大代价为O(n),故总的时间复杂度为O(nm)。对于n=50000,m=50000的数据量来说,这样时间复杂度显然是不能够接受的。

    仔细观察我们每一次的操作,其中CMD3是对整体进行了平移,CMD1,CMD2,CMD4都是针对i到j的一个区间进行操作。

    首先我们来解决看似比较简单的CMD3操作:

    若将整个字符串s看作环形,则线型的字符串是从起点指针SP开始顺时针将n个元素进行展开得到的。那么CMD3操作为顺时针移动该环的头指针。举个例子来说:

    CMD3

    最开始头指针在1时,我们展开字符串为[1,2,3,4,5]。当执行CMD3 K=2操作后,起点指针SP移动到3的位置,此时展开的字符串为[3,4,5,1,2]。

    符合CMD3操作的规则,并且起点指针SP的改变就是增加了K。

    其中新字符串的第i~j位,对应的是原字符串第i+SP~j+SP位。

    所以我们只需要维护一个SP指针,当执行CMD3操作时,改变SP的值。而对于其他操作的区间,只需要将区间从[i..j]变化到[i+SP..j+SP]即可。

    需要注意的是,SP,i+SP,j+SP有可能会超过n。当超过n时,需要将其值减去n。

    至此执行CMD3操作的时间复杂度降至O(1)。

    接下来考虑CMD1,CMD2,CMD4。这三个操作均为区间上的操作,因此我们可以使用线段树来进行模拟。(在我们的Hiho一下第19期第20期可以找到线段树的教程)

    在那之前,我们需要对字符进行处理。从题目中我们知道当一个字符超过'Z'时,会直接变成'A'。所以我们可以直接考虑将'A'~'Z'与0~25对应起来。当一个字符增加了很多次K后,其实际表示的字符也就等于该值 mod 26。

    构造线段树

    构造线段树,主要是构造每个节点的数据域,使其能够记录我们需要的信息,同时在父节点和子节点之间能够进行信息的传递。根据本题的题意,我们构造的线段树其节点包含以下三个数据:

    • same: 表示当前区间的字符是否相同,若相同则same等于该字符,否则same=-1
    • add: 表示当前区间的增量,对应CMD2操作所增加的K
    • delta和 inc : 这两个变量是一组,其表示CMD4的操作。其含义为,该区间最左起第1个元素值增量为delta,此后每一个元素的增量比前一个多inc。即第2个元素的增量为delta+inc,第3个元素的增量为delta+inc+inc,...,第i个元素的增量为delta+inc*(i-1)。举个例子:

      若我们对区间[1,3]进行了CMD4操作,实际的意义为s1+1,s[2]+2,s[3]+3。对于表示区间[1,3]的节点,其Delta=1,inc=1。

      若我们对区间[1,3]进行了2次CMD4操作,实际意义为s1+2,s[2]+4,s[3]+6。则此时Delta=2,inc=2。而对于表示区间[2,3]的节点,其Delta=4,inc=2。因为该区间左起第1个元素为s[2]+4,故delta=4。

    在本题中我们一开始便读入了字符串,该字符串的每一个字符对应了树的一个叶子节点。故我们一开始就需要建出整颗树,其代码:

    // 该段代码我们采用的是数组模拟线段树
    const int MAXN = 50001;
    
    struct sTreeNode {
        int left, right;
        int same, add;
        int delta, inc;
        int lch, rch;
    } tree[ MAXN << 2 ];
    
    void createTree(int rt, int left, int right) {
        tree[rt].left = left, tree[rt].right = right;
        tree[rt].delta = tree[rt].step = 0;
        tree[rt].add = 0;
    
        if (left == right) {    // 叶子节点
            tree[rt].base = str[ left ] - 'A';
            tree[rt].lch = tree[rt].rch = 0;
            return ;
        }
    
        // 非叶子节点
        tree[rt].base = -1;
        tree[rt].lch = rt * 2, tree[rt].rch = rt * 2 + 1;
    
        int mid = (tree[rt].left + tree[rt].right) >> 1;
        createTree(tree[rt].lch, left, mid);
        createTree(tree[rt].rch, mid + 1, right);
        return ;
    }

    更新线段树

    在更新线段树时,需要注意更新区间可能会出现i+SP <= n并且j+SP大于n时,此时要将区间分为[i+SP..n]和[1..j+SP-n]两个部分单独处理。

    更新线段树信息的update函数:

    // rt表示当前节点
    // left,right表示此次操作的区间
    // key表示此次操作K或Delta
    // type表示此次操作的类型
    void update(int rt, int left, int right, int key, int type) {
        if (!rt) return ;
        if (tree[rt].right < left || tree[rt].left > right) return ;
        if (left <= tree[rt].left && tree[rt].right <= right) {
            // 当前节点区间完全包含于[left,right]
            // 更新当前区间信息
            ...
        }  else {
            // 当前节点区间不完全包含于[left,right],则需要让子区间来处理
            // 传递当前区间的信息
            ...            
    
            // 更新当前区间信息
            ...
    
            // 迭代处理
            update(tree[rt].lch, left, right, key, type);
            update(tree[rt].rch, left, right, key, type);
        }
        return ;
    }

    若当前区间包含于[left,right],根据操作的不同我们进行如下的处理:

    • CMD1: 直接更新区间的same值,同时将add,delta和inc置为0
      
      if (type == 1) {
          tree[rt].same = key;
          tree[rt].delta = 0, tree[rt].inc = 0;
          tree[rt].add = 0;
      }
    • CMD2: 累加到当前区间的add上
      
      if (type == 2) {
          tree[rt].add += key;
      }
    • CMD4: 将新的delta和inc累加到当前区间的delta和inc上
      
      if (type == 4) {
          tree[rt].delta += key + (tree[rt].left - left);
          tree[rt].inc ++;
      }

    当需要对子区间进行处理时,我们需要将当前区间的信息传递下去,此时需要判断当前区间的same值:

    // 传递当前区间的信息
    int mid = (tree[rt].left + tree[rt].right) / 2;
    
    if (tree[rt].base == -1) {
    // lch
        tree[ tree[rt].lch ].delta += tree[rt].delta;
        tree[ tree[rt].lch ].step  += tree[rt].step;
        tree[ tree[rt].lch ].add   += tree[rt].add;
        // rch
        tree[ tree[rt].rch ].delta += tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
        tree[ tree[rt].rch ].step  += tree[rt].step;
        tree[ tree[rt].rch ].add   += tree[rt].add;
    }   else {
        tree[ tree[rt].lch ].base = tree[ tree[rt].rch ].base = tree[rt].base;
        tree[ tree[rt].lch ].delta = tree[rt].delta;
        tree[ tree[rt].rch ].delta = tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
        tree[ tree[rt].lch ].step = tree[ tree[rt].rch ].step = tree[rt].step;
        tree[ tree[rt].lch ].add  = tree[ tree[rt].rch ].add  = tree[rt].add;
    }

    当我们把当前区间的信息传递下去后,可以知道当前区间内的字符一定会发生改变,所以设置其same=1。同时由于当前区间的add,delta和inc信息已经传递下去,其本身的add,delta和inc设置为0:

    // 更新当前区间信息
    tree[rt].base = -1;
    tree[rt].delta = tree[rt].step = 0;
    tree[rt].add = 0;

    产生新的字符串

    在这一步我们需要对整个线段树进行一次遍历,将所有的信息传递到叶子节点,再根据叶子节点的值产生我们新的字符串。

    int f[ MAXN ];    // 记录每个叶子节点的数值
    void getResult(int rt) {
        if (!rt) return ;
        if (tree[rt].base != -1) {
            int delta = tree[rt].delta;
            for (int i = tree[rt].left; i <= tree[rt].right; ++i)
                f[i] = tree[rt].base + tree[rt].add + delta, delta += tree[rt].step;
        } else {
            int mid = (tree[rt].left + tree[rt].right) / 2;
            // lch
            tree[ tree[rt].lch ].delta += tree[rt].delta;
            tree[ tree[rt].lch ].step  += tree[rt].step;
            tree[ tree[rt].lch ].add   += tree[rt].add;
            // rch
            tree[ tree[rt].rch ].delta += tree[rt].delta + (mid - tree[rt].left + 1) * tree[rt].step;
            tree[ tree[rt].rch ].step  += tree[rt].step;
            tree[ tree[rt].rch ].add   += tree[rt].add;
    
            getResult(tree[rt].lch);
            getResult(tree[rt].rch);
        }
        return ;
    }

    此时得到的s并不是我们最后的结果,还需要根据SP的值来输出

    void typeAns() {
        for (int i = 0; i < n; ++i)
            printf("%c", (char) (f[(SP + i) % n] + 'A'));
        printf("
    ");
        return ;
    }
    #include <iostream>
    #include <algorithm>
    #include <string.h>
    #include <stdio.h>
    #include <cmath>
    #include <queue>
    #include <map>
    #define maxn 50000 + 100
    using namespace std;
    string ch;
    char alpha[30] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
    };
    void dfs(int u, int v, int tt)
    {
        int res = 1;
        for(int i = u - 1; i <= v - 1; i++)
        {
            int tt = (ch[i] - 'A' + res) % 26;
            ch[i] = alpha[tt];
            res++;
        }
        return;
    }
    int main()
    {
        int n, m;
        scanf("%d %d", &n, &m);
        cin >> ch;
        int op;
        int u, v, w;
        char c[10];
        char cc[10];
        while(m--)
        {
            cin >> cc >> op;
            if(op == 1)
            {
                scanf("%d %d %s", &u, &v, c);
                for(int i = u - 1; i <= v - 1; i++)
                {
                    ch[i] = c[0];
                }
                //cout << ch << endl;
            }
            else if(op == 2)
            {
                scanf("%d %d %d", &u, &v, &w);
                w = w % 26;
                for(int i = u - 1; i <= v - 1; i++)
                {
                    int tt = (ch[i] - 'A' + w) % 26;
                    ch[i] = alpha[tt];
                }
                // cout << ch << endl;
            }
            else if(op == 3)
            {
                scanf("%d", &w);
                string ch1 = ch;
                string s1 = ch.substr(0, w);
                string s2 = ch1.substr(w, n - w + 1);
                ch = "";
                ch = s2 + s1;
                //cout << ch << endl;
            }
            else if(op == 4)
            {
                scanf("%d %d", &u, &v);
                //printf("%d %d %d
    ", u, v, w);
                dfs(u, v, w);
                // cout << ch << endl;
            }
        }
        cout << ch << endl;
        return 0;
    }
    TLE暴力了一次

    根据题意线段树:

    #include <iostream>
    #include <algorithm>
    #include <cmath>
    #include <string.h>
    #include <stdio.h>
    #include <queue>
    using namespace std;
    #define maxn 50000 + 100
    int len;
    char alpha[30] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
    };
    int sp = 0;
    using namespace std;
    char ch[maxn];
    struct Tire
    {
        int same; ///表示当前区间的字符是否相同,若相同则same等于该字符,否则same=-1
        int add;   ///表示当前区间的增量,对应CMD2操作所增加的K
        int delta; ///该区间最左起第1个元素值增量为delta
        int inc;    ///每一个元素的增量比前一个多
        int val;    ///当前的值
    } tree[maxn << 2];
    void build(int left, int right, int root)
    {
        tree[root].same = -1;
        tree[root].add = 0;
        tree[root].delta = 0;
        tree[root].inc = 0;
        tree[root].val = 0;
        if(left == right)
        {
            return;
        }
        int mid = (left + right) >> 1;
        build(left, mid, root << 1);
        build(mid + 1, right, root << 1 | 1);
        return;
    }
    void change(int& x, int y) {
        x += y;
        x %= 26;
    }
    
    void getid(int s, int e, int& s1, int& e1, int& s2, int& e2) {
        s--;
        e--;
        s1 = s2 = e1 = e2 = -2;
        int t1 = sp + s, t2 = sp + e;
        if(t1 < len && t2 < len) {
            s1 = t1;
            e1 = t2;
            s2 = e2 = -2;
        }
        else if(t1 < len && t2 >= len) {
            t2 %= len;
            s1 = t1;
            e1 = len - 1;
            s2 = 0;
            e2 = t2;
        }
        else if(t1 >= len && t2 >= len) {
            t1 %= len;
            t2 %= len;
            s1 = t1;
            e1 = t2;
            s2 = e2 = -2;
        }
        s1++;
        e1++;
        s2++;
        e2++;
    }
    void pushup(int left, int right, int root)
    {
        if(left == right)
        {
            return;
        }
        if(tree[root].same >= 0)
        {
            tree[root << 1].same = tree[root << 1].val = tree[root].same;
            tree[root << 1 | 1].same = tree[root << 1 | 1].val = tree[root].same;
            tree[root].same = -1;
            tree[root << 1].add = tree[root << 1].delta = tree[root << 1].inc = 0;
            tree[root << 1 | 1].add = tree[root << 1 | 1].delta = tree[root << 1 | 1].inc = 0;
        }
        if(tree[root].add >= 0)
        {
            change(tree[root << 1].add, tree[root].add);
            change(tree[root << 1 | 1].add, tree[root].add);
            tree[root].add = 0;
        }
        if(tree[root].delta >= 0)
        {
            change(tree[root << 1].delta, tree[root].delta);
            change(tree[root << 1].inc, tree[root].inc);
            change(tree[root << 1 | 1].delta, tree[root].delta + ((right - left) / 2 + 1)*tree[root]. inc);
            change(tree[root << 1 | 1].inc, tree[root].inc);
            tree[root].delta = 0;
            tree[root].inc = 0;
        }
        return;
    }
    
    void update(int op, int L, int R, int root, int left, int right, int val)
    {
        pushup(left, right, root);
        if(L <= left && right <= R)
        {
            if(op == 2)
            {
                change(tree[root].add, val);
            }
            else if(op == 4)
            {
                change(tree[root].inc, 1);
                change(tree[root].delta,  left - L  + val);
            }
            else if(op == 1)
            {
                tree[root].same = val;
                tree[root].val = val;
                tree[root].add = 0;
                tree[root].delta = 0;
                tree[root].inc = 0;
            }
            return;
        }
        int mid = (left + right) / 2;
        if(mid >= L) {
            update(op, L, R, root << 1, left, mid, val);
        }
        if(R > mid) {
            update(op, L, R, root << 1 | 1, mid + 1, right, val);
        }
        return;
    }
    int  query(int L, int R, int root, int left, int right)
    {
        pushup(left, right, root);
        if(L <= left && R >= right)
        {
            return (tree[root].val + tree[root].add + tree[root].delta) % 26;
        }
        int mid = (left + right) >> 1;
        if(L <= mid) {
            return  query(L, R, root << 1, left, mid);
        }
        if(R > mid) {
            return  query(L, R, root << 1 | 1, mid + 1, right);
        }
        return 0;
    }
    int main()
    {
        int n, m;
        scanf("%d %d", &n, &m);
        cin >> ch;
        build(1, n, 1);
        for(int i = 0; i < n; i++)
        {
            update(1, i + 1, i + 1, 1, 1, n, ch[i] - 'A');
        }
        sp = 0;
        int op;
        int u, v, w;
        char c[10];
        char cc[10];
        int s1, e1, s2, e2;
        len = n;
        for(int jj = 0; jj < m; jj++)
        {
            cin >> cc >> op;
            if(op == 1)
            {
                cin >> u >> v >> c;
                getid(u, v, s1, e1, s2, e2);
                update(1, s1, e1, 1, 1, n, c[0] - 'A');
                if(s2 != -1 && e2 != -1) {
                    update(1, s2, e2, 1, 1, n, c[0] - 'A');
                }
            }
            else if(op == 2)
            {
                cin >> u >> v >> w;
                getid(u, v, s1, e1, s2, e2);
                update(2, s1, e1, 1, 1, n, w);
                if(s2 != -1 && e2 != -1) {
                    update(2, s2, e2, 1, 1, n, w);
                }
            }
            else if(op == 3)
            {
                scanf("%d", &w);
                sp = sp + w;
                sp = sp % n;
            }
            else if(op == 4)
            {
                cin >> u >> v;
                getid(u, v, s1, e1, s2, e2);
                update(4, s1, e1, 1, 1, n, 1);
                if(s2 != -1 && e2 != -1)
                {
                    update(4, s2, e2, 1, 1, n, e1 - s1 + 2);
                }
            }
        }
        int  tt = sp;
        for(int i = 0; i < n; i++)
        {
            printf("%c", 'A' + query(tt + 1, tt + 1, 1, 1, n));
            tt++;
            tt %= n;
        }
        return 0;
    }
    View Code
  • 相关阅读:
    GC之五--SystemGC完全解读
    Java堆外内存之二:堆外内存使用总结
    Spring @Async之二:SpringBoot 自定义线程池,修改springboot的默认线程池
    JVM内存模型
    JDK源码包结构分类
    命令查看java的class字节码文件、verbose、synchronize、javac、javap
    重谈设计
    站点架构模式
    愚人节,不恶搞,我们玩真的。不服来战!
    Project Euler:Problem 86 Cuboid route
  • 原文地址:https://www.cnblogs.com/chenyang920/p/4782289.html
Copyright © 2011-2022 走看看