时间限制: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操作为顺时针移动该环的头指针。举个例子来说:

最开始头指针在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 = ; struct sTreeNode {
int left, right;
int same, add;
int delta, inc;
int lch, rch;
} tree[ MAXN << ]; void createTree(int rt, int left, int right) {
tree[rt].left = left, tree[rt].right = right;
tree[rt].delta = tree[rt].step = ;
tree[rt].add = ; if (left == right) { // 叶子节点
tree[rt].base = str[ left ] - 'A';
tree[rt].lch = tree[rt].rch = ;
return ;
} // 非叶子节点
tree[rt].base = -;
tree[rt].lch = rt * , tree[rt].rch = rt * + ; int mid = (tree[rt].left + tree[rt].right) >> ;
createTree(tree[rt].lch, left, mid);
createTree(tree[rt].rch, mid + , 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 == ) {
    tree[rt].same = key;
    tree[rt].delta = , tree[rt].inc = ;
    tree[rt].add = ;
    }
  • CMD2: 累加到当前区间的add上
    
    if (type == ) {
    tree[rt].add += key;
    }
  • CMD4: 将新的delta和inc累加到当前区间的delta和inc上
    
    if (type == ) {
    tree[rt].delta += key + (tree[rt].left - left);
    tree[rt].inc ++;
    }

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

// 传递当前区间的信息
int mid = (tree[rt].left + tree[rt].right) / ; if (tree[rt].base == -) {
// 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 + ) * 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 + ) * 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 = -;
tree[rt].delta = tree[rt].step = ;
tree[rt].add = ;

产生新的字符串

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

int f[ MAXN ];    // 记录每个叶子节点的数值
void getResult(int rt) {
if (!rt) return ;
if (tree[rt].base != -) {
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) / ;
// 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 + ) * 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 = ; i < n; ++i)
printf("%c", (char) (f[(SP + i) % n] + 'A'));
printf("\n");
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[] = {
'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 = ;
for(int i = u - ; i <= v - ; i++)
{
int tt = (ch[i] - 'A' + res) % ;
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[];
char cc[];
while(m--)
{
cin >> cc >> op;
if(op == )
{
scanf("%d %d %s", &u, &v, c);
for(int i = u - ; i <= v - ; i++)
{
ch[i] = c[];
}
//cout << ch << endl;
}
else if(op == )
{
scanf("%d %d %d", &u, &v, &w);
w = w % ;
for(int i = u - ; i <= v - ; i++)
{
int tt = (ch[i] - 'A' + w) % ;
ch[i] = alpha[tt];
}
// cout << ch << endl;
}
else if(op == )
{
scanf("%d", &w);
string ch1 = ch;
string s1 = ch.substr(, w);
string s2 = ch1.substr(w, n - w + );
ch = "";
ch = s2 + s1;
//cout << ch << endl;
}
else if(op == )
{
scanf("%d %d", &u, &v);
//printf("%d %d %d\n", u, v, w);
dfs(u, v, w);
// cout << ch << endl;
}
}
cout << ch << endl;
return ;
}

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[] = {
'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 = ;
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 << ];
void build(int left, int right, int root)
{
tree[root].same = -;
tree[root].add = ;
tree[root].delta = ;
tree[root].inc = ;
tree[root].val = ;
if(left == right)
{
return;
}
int mid = (left + right) >> ;
build(left, mid, root << );
build(mid + , right, root << | );
return;
}
void change(int& x, int y) {
x += y;
x %= ;
} void getid(int s, int e, int& s1, int& e1, int& s2, int& e2) {
s--;
e--;
s1 = s2 = e1 = e2 = -;
int t1 = sp + s, t2 = sp + e;
if(t1 < len && t2 < len) {
s1 = t1;
e1 = t2;
s2 = e2 = -;
}
else if(t1 < len && t2 >= len) {
t2 %= len;
s1 = t1;
e1 = len - ;
s2 = ;
e2 = t2;
}
else if(t1 >= len && t2 >= len) {
t1 %= len;
t2 %= len;
s1 = t1;
e1 = t2;
s2 = e2 = -;
}
s1++;
e1++;
s2++;
e2++;
}
void pushup(int left, int right, int root)
{
if(left == right)
{
return;
}
if(tree[root].same >= )
{
tree[root << ].same = tree[root << ].val = tree[root].same;
tree[root << | ].same = tree[root << | ].val = tree[root].same;
tree[root].same = -;
tree[root << ].add = tree[root << ].delta = tree[root << ].inc = ;
tree[root << | ].add = tree[root << | ].delta = tree[root << | ].inc = ;
}
if(tree[root].add >= )
{
change(tree[root << ].add, tree[root].add);
change(tree[root << | ].add, tree[root].add);
tree[root].add = ;
}
if(tree[root].delta >= )
{
change(tree[root << ].delta, tree[root].delta);
change(tree[root << ].inc, tree[root].inc);
change(tree[root << | ].delta, tree[root].delta + ((right - left) / + )*tree[root]. inc);
change(tree[root << | ].inc, tree[root].inc);
tree[root].delta = ;
tree[root].inc = ;
}
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 == )
{
change(tree[root].add, val);
}
else if(op == )
{
change(tree[root].inc, );
change(tree[root].delta, left - L + val);
}
else if(op == )
{
tree[root].same = val;
tree[root].val = val;
tree[root].add = ;
tree[root].delta = ;
tree[root].inc = ;
}
return;
}
int mid = (left + right) / ;
if(mid >= L) {
update(op, L, R, root << , left, mid, val);
}
if(R > mid) {
update(op, L, R, root << | , mid + , 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) % ;
}
int mid = (left + right) >> ;
if(L <= mid) {
return query(L, R, root << , left, mid);
}
if(R > mid) {
return query(L, R, root << | , mid + , right);
}
return ;
}
int main()
{
int n, m;
scanf("%d %d", &n, &m);
cin >> ch;
build(, n, );
for(int i = ; i < n; i++)
{
update(, i + , i + , , , n, ch[i] - 'A');
}
sp = ;
int op;
int u, v, w;
char c[];
char cc[];
int s1, e1, s2, e2;
len = n;
for(int jj = ; jj < m; jj++)
{
cin >> cc >> op;
if(op == )
{
cin >> u >> v >> c;
getid(u, v, s1, e1, s2, e2);
update(, s1, e1, , , n, c[] - 'A');
if(s2 != - && e2 != -) {
update(, s2, e2, , , n, c[] - 'A');
}
}
else if(op == )
{
cin >> u >> v >> w;
getid(u, v, s1, e1, s2, e2);
update(, s1, e1, , , n, w);
if(s2 != - && e2 != -) {
update(, s2, e2, , , n, w);
}
}
else if(op == )
{
scanf("%d", &w);
sp = sp + w;
sp = sp % n;
}
else if(op == )
{
cin >> u >> v;
getid(u, v, s1, e1, s2, e2);
update(, s1, e1, , , n, );
if(s2 != - && e2 != -)
{
update(, s2, e2, , , n, e1 - s1 + );
}
}
}
int tt = sp;
for(int i = ; i < n; i++)
{
printf("%c", 'A' + query(tt + , tt + , , , n));
tt++;
tt %= n;
}
return ;
}

hihocoder-第六十一周 Combination Lock的更多相关文章

  1. hihocoder #1058 Combination Lock

    传送门 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Finally, you come to the interview room. You know that a ...

  2. 201521123061 《Java程序设计》第十一周学习总结

    201521123061 <Java程序设计>第十一周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 本周学习的是如何解决多线程访问中的互斥 ...

  3. 201521123072《java程序设计》第十一周学习总结

    201521123072<java程序设计>第十一周学习总结 1. 本周学习总结 2. 书面作业 本次PTA作业题集多线程 互斥访问与同步访问 完成题集4-4(互斥访问)与4-5(同步访问 ...

  4. 201521123038 《Java程序设计》 第十一周学习总结

    201521123038 <Java程序设计> 第十一周学习总结 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多 ...

  5. 201521123122 《java程序设计》第十一周学习总结

    ## 201521123122 <java程序设计>第十一周实验总结 ## 1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 其实这周也没讲多少内容,所 ...

  6. 201621123040《Java程序设计》第十一周学习总结

    1.本周学习总结 1.1以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2.书面作业 2.1源代码阅读:多线程程序BounceThread 2.1.1BallRunnable类有什么用?为什 ...

  7. 杨其菊/常惠琢《面向对象程序设计(java)》第十一周学习总结

    <面向对象程序设计>第十一周学习总结 第一部分:理论知识 JAVA的集合框架 JAVA的集合框架实现对各种数据结构的封装,以降低对数据管理与处理的难度. 所谓框架就是一个类库的集合,框 ...

  8. 20172325 2017-2018-2 《Java程序设计》第十一周学习总结

    20172325 2017-2018-2 <Java程序设计>第十一周学习总结 教材学习内容总结 Android简介 Android操作系统是一种多用户的Linux系统,每个应用程序作为单 ...

  9. 第十一周PSP&进度条

    PSP 一.表格: D日期     C类型 C内容 S开始时间 E结束时间 I时间间隔 T净时间(mins) 预计花费时间(mins) 11月24号 站立会议 分配任务&设计final方案 1 ...

随机推荐

  1. BZOJ 4592 SHOI2015 脑洞治疗仪 线段树

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4592 题意概述:需要维护一个01序列A,一开始A全部都是1.支持如下操作: 1.将区间[l ...

  2. 编程练习:寻找发帖"水王"

    题目: 寻找发帖"水王" 来源: 编程之美 分析 衍生:就是给定一个数组,其中某个元素出现次数超过了数组长度的一半,找出这个元素 方法s 方法1 对这个串进行遍历,同时对出现的元素 ...

  3. 结对作业 -GUI四则运算

    目录: 一.前言(及项目地址) 二.PSP(planning) 三.结对编程中对接口的设计 四.计算模块接口的设计与实现过程 五.计算模块接口部分的性能改进 六.计算模块部分单元测试展示 七.计算模块 ...

  4. javascript获取和判断浏览器窗口、屏幕、网页的高度、宽度等

    主要介绍了javascript获取和判断浏览器窗口.屏幕.网页的高度.宽度等 scrollHeight: 获取对象的滚动高度.scrollLeft:设置或获取位于对象左边界和窗口中目前可见内容的最左端 ...

  5. Python执行Linux系统命令的4种方法

    http://www.jb51.net/article/56490.htm (1) os.system 仅仅在一个子终端运行系统命令,而不能获取命令执行后的返回信息 复制代码代码如下: system( ...

  6. java生成唯一的id编号

    GUID是一个128位长的数字,一般用16进制表示.算法的核心思想是结合机器的网卡.当地时间.一个随即数来生成GUID.从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义 ...

  7. Android 多屏幕适配 dp和px的关系

    一直以来别人经常问我,android的多屏幕适配到底是怎么弄,我也不知道如何讲解清楚,或许自己也是挺迷糊. 以下得出的结论主要是结合官方文档进行分析的https://developer.android ...

  8. Spring之JDBC

    jdbc.properties driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/ssi?useUnicode ...

  9. BZOJ4484 JSOI2015最小表示(拓扑排序+bitset)

    考虑在每个点的出边中删除哪些.如果其出边所指向的点中存在某点能到达另一点,那么显然指向被到达点的边是没有用的.于是拓扑排序逆序处理,按拓扑序枚举出边,bitset维护可达点集合即可. #include ...

  10. SMT(SF)

    示例一: uint iPwmDuty; double temp; temp = (double)AdConvert(AN_TEMPERATURE); temp = temp/; iPwmDuty = ...