时间限制: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. URAL 1732 Ministry of Truth(KMP)

    Description In whiteblack on blackwhite is written the utterance that has been censored by the Minis ...

  2. mac下使用clion构建boost库

    mac下使用clion构建boost库 使用brew install boost 完成后发现boost被安装在在/usr/local/Cellar/boost下 jetbrain给出的指导意见 htt ...

  3. Java 端口扫描器 TCP的实现方法

    想必很多朋友都实现过一个简易的聊天室这个功能,其中涉及到Socket套接字这个类,我们通过一个特定的IP以及特定的端口创建一个服务端的套接字(ServerSocket),以此我们聊天个体的套接字(So ...

  4. sql声明变量存储查询结果

    with t as 查到条件数据,然后在下面使用到t,用exists做判断会非常慢,改成left join会快很多. 我使用的数据库时2008Sql r2. 文章:SQL数据库中临时表.临时变量和WI ...

  5. windows redis+lua的调试

    1.编写lua脚本my.lua local key = KEYS[1] --限流KEY(一秒一个) local limit = tonumber(ARGV[1]) --限流大小 local curre ...

  6. 习题:就是干(DP)

    洛谷2301 题目描述 眼看着老师大军浩浩荡荡的向机房前进.LOI 的同学们决定动用自己的力量来保卫他们的好朋友loidc.现在每个人都要挑选自己的武器——两根木棍.一根用做远距离投掷,另一根用做近距 ...

  7. [bzoj1052] [HAOI2007]覆盖问题

    Description 某人在山上种了N棵小树苗.冬天来了,温度急速下降,小树苗脆弱得不堪一击,于是树主人想用一些塑料薄膜把这些小树遮盖起来,经过一番长久的思考,他决定用3个L * L的正方形塑料薄膜 ...

  8. Linux上 Can't connect to X11 window server using XX as the value of the DISPLAY 错误解决方法

    在Linux上运行需要图形界面的程序时出现如下错误提示: No protocol specified Exception in thread "main" java.awt.AWT ...

  9. Spring AOP execution表达式

    Spring中事务控制相关配置: <bean id="txManager" class="org.springframework.jdbc.datasource.D ...

  10. 近期对于windows服务的理解

    1.APP.config的作用   在开发环境下时,根目录下的APP.config里面会填写一些参数之类的.当生成之后,这些参数将会被自动生成在*.exe文件目录中.如图: 其中,.exe文件为Win ...