微软笔试题<Combination Lock>
2015-08-30 10:00
176 查看
题意分析
给定一个字符串s,以及对该字符串s的 m 个操作。字符串s包含n个字符,下标为1..n。字符由'A'到'Z'构成,字符增加1表示该字符变为后续字符,比如
'A'增加1是
'B',
'C'增加1是
'D'。需要注意的是
'Z'增加1是
'A'。
m个操作包含以下四种类型:
将字符串第i位到第j位设定为C。
比如当i=2,j=3,C='Z'时:
"ABCDEFG"变成
"AZZDEFG"
将字符串第i位到第j位增加K。
比如i=2,j=3,K=1时:
"ABCDEFG"变成
"ACDDEFG"
将字符串左边K位移至右边。
比如K=3时:
"ABCDEFG"变成
"DEFGABC"
从字符串第i位到第j位,依次增加1,2,...,j-i+1。
比如当i=2,j=3时:
"ABCDEFG"变成
"ACEDEFG"
输出m个操作结束后的字符串s。
本题目是个中档的线段树题目,由于不需要向上维护信息,所以只着重解决维护标记的设计,和是重叠问题,
标记的设计的设计和属性的设计有个明显的区别在于,标记可以"重叠","下推",那么本题目除了单点之外,其他点不需要维护实质属性,所以标记只要保存信息能够推到底部就行,
下面是来自hihocoder讨论区的解答:
算法分析
本题需要根据每一次的操作去修改现在的s。若采用朴素的做法,每一次修改其最大代价为O(n),故总的时间复杂度为O(nm)。对于n=50000,m=50000的数据量来说,这样时间复杂度显然是不能够接受的。仔细观察我们每一次的操作,其中CMD3是对整体进行了平移,CMD1,CMD2,CMD4都是针对i到j的一个区间进行操作。
首先我们来解决看似比较简单的CMD3操作:
若将整个字符串s看作环形,则线型的字符串是从起点指针SP开始顺时针将n个元素进行展开得到的。那么CMD3操作为顺时针移动该环的头指针。举个例子来说:
![](http://media.hihocoder.com/contests/hiho61/61_2.png)
最开始头指针在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。
在本题中我们一开始便读入了字符串,该字符串的每一个字符对应了树的一个叶子节点。故我们一开始就需要建出整颗树,其代码:
[code]// 该段代码我们采用的是数组模拟线段树 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函数:
[code]// 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
[code]if (type == 1) { tree[rt].same = key; tree[rt].delta = 0, tree[rt].inc = 0; tree[rt].add = 0; }
CMD2: 累加到当前区间的add上
[code]if (type == 2) { tree[rt].add += key; }
CMD4: 将新的delta和inc累加到当前区间的delta和inc上
[code]if (type == 4) { tree[rt].delta += key + (tree[rt].left - left); tree[rt].inc ++; }
当需要对子区间进行处理时,我们需要将当前区间的信息传递下去,此时需要判断当前区间的same值:
[code]// 传递当前区间的信息 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:
[code]// 更新当前区间信息 tree[rt].base = -1; tree[rt].delta = tree[rt].step = 0; tree[rt].add = 0;
产生新的字符串
在这一步我们需要对整个线段树进行一次遍历,将所有的信息传递到叶子节点,再根据叶子节点的值产生我们新的字符串。[code]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的值来输出
[code]void typeAns() { for (int i = 0; i < n; ++i) printf("%c", (char) (f[(SP + i) % n] + 'A')); printf("\n"); return ; }
结果分析
本题现场的通过率为2%,一个有9名选手。本题的思维复杂度并没有第三题高,但是代码量比较大。因此在前三题花费了较多精力的选手很难有足够的时间来解决此题。
不过也有少数选手选择放弃了第三题,直接从第四题入手并得到了满分。
对于实际比赛来说,当拿到第四题这样的题目,而时间又不足够充裕时,直接采用朴素算法获得部分分也是一个较好的选择。
下面是我自的代码,由于把标记进行了顺序下推,所以比分析中写的略微简单,但是下面代码并不知道有没有bug,毕竟没有地方交题。
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cstring> #include <map> #include <set> #include <vector> #include <cctype> #include <cmath> #include <queue> #define ls rt<<1 #define rs rt<<1|1 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define mem(a,n) memset(a,n,sizeof(a)) #define rep(i,n) for(int i=0;i<(int)n;i++) #define rep1(i,x,y) for(int i=x;i<=(int)y;i++) using namespace std; typedef pair<int,int> pii; typedef long long ll; const int inf = 0x3f3f3f3f; const ll oo = 1e12; const int N = 50001; struct node{ int base , tran , delta ,inc; void push_tran(int v){ tran = v; delta = 0; inc = 0; } void push_add(int v1 , int v2){ delta+=v1, inc+=v2; } }a[N<<2]; char str ; void build(int l,int r,int rt){ a[rt].base = a[rt].tran = a[rt].delta = a[rt].inc = 0; if(l == r){ a[rt].base = str[l]; return ; } int m=(l+r)>>1; build(lson); build(rson); } void push_down(int l,int r,int rt){ if(a[rt].tran){ a[ls].push_tran(a[rt].tran); a[rs].push_tran(a[rt].tran); a[rt].tran = 0; } int m =(l+r)>>1; a[ls].push_add(a[rt].delta,a[rt].inc); a[rs].push_add(a[rt].delta+(m-l+1)*a[rt].inc,a[rt].inc); a[rt].inc = a[rt].delta = 0; } void mov1(int l,int r,int rt,int L, int R,int C){ if(L<=l && r<=R){ a[rt].push_tran(C); return ; } push_down(l,r,rt); int m=(l+r)>>1; if(L<=m) mov1(lson,L,R,C); if(R> m) mov1(rson,L,R,C); } void mov2(int l,int r,int rt,int L,int R,int add,int inc){ if(L<=l && r<=R){ a[rt].push_add(add,inc); return ; } push_down(l,r,rt); int m=(l+r)>>1; if(L<=m) mov2(lson,L,R,add,inc); if(R> m) mov2(rson,L,R,add+(m-L+1)*inc,inc); } int query(int l,int r,int rt,int po){ if(l == r){ if(a[rt].tran) a[rt].base = a[rt].tran; a[rt].base+=a[rt].delta; return a[rt].base; } push_down(l,r,rt); int m = (l+r)>>1; if(po<=m) return query(lson,po); if(po >m) return query(rson,po); } int n,m; int main() { while(scanf("%d %d",&n,&m)==2) { scanf("%s",str+1) , build(1,n,1); int cmd , x, y, c; char val[10]; int now = 0; while(m--){ scanf("%d",&cmd); if(cmd == 1){ scanf("%d %d %s",&x,&y,val); x=(x+now)%n, y=(y+now)%n; if(x <= y){ mov1(1,n,1,x,y,val[0]); } else { mov1(1,n,1,x,n,val[0]); mov1(1,n,1,1,y,val[0]); } } else if(cmd == 2){ scanf("%d %d %d",&x, &y, &c); x=(x+now)%n, y=(y+now)%n; if(x <= y){ mov2(1,n,1,x,y,c,0); } else { mov2(1,n,1,x,n,c,0); mov2(1,n,1,1,y,c,0); } } else if(cmd == 3){ scanf("%d",&c) , now = (now + c)%n; } else { scanf("%d %d",&x, &y); x=(x+now)%n, y=(y+now)%n; if(x <= y){ mov2(1,n,1,x,y,1,1); } else { mov2(1,n,1,x,n,1,1); mov2(1,n,1,1,y,1,1); } } } for(int i=now , j = 1;j<=n;i=(i+1)%n,j++){ printf("%c",(char)query(1,n,1,i+1)); } printf("\n"); } return 0; } /* 7 1 ABCDEFG 1 2 3 Z */
相关文章推荐
- R概率分布函数使用小结
- Codeforces Round #318 574A Bear and Elections(优先队列)
- Windows10的快捷键和新功能你利用了多少?
- OSPF广播多路访问配置
- 在二叉树中找值为x的结点(假设所有结点的值都不一样)
- redis学习笔记。
- find搜索命令
- android自定义时间选择器
- Til the Cows Come Home(最短路径)
- 巧用runtime遍历类的重要信息
- 利用非递归方法实现二叉树的中序遍历
- 【数据库学习】机房收费系统(一)学生上下机
- Spring MVC 是什么
- CF-455A
- HP JAVA第一周
- 黑马程序员 设计模式
- 数据库生成带框架、数据的脚本和还原数据库
- 利用非递归方法实现二叉树的先序遍历
- <table>标签
- OSPF点对点配置