移动盒子 Boxes in a Line
题目描述
你有n个盒子在桌子上的一条线上从左到右编号为1……n。你的任务是模拟四种操作
1 X Y 移动盒子编号X到盒子编号Y的左边(如果X已经在Y的左边了就忽略)
2 X Y 移动盒子编号X到盒子编号Y的右边(如果X已经在Y的右边了就忽略)
3 X Y 交换盒子编号X与盒子编号Y的位置
4 将整条线反转
操作保证合法,X不等于Y
举一个例子,如果n=6,操作 1 1 4然后就变成了2 3 1 4 5 6;再操作 2 3 5就变成了 2 1 4 5 3 6;再操作 3 1 6 就变成 2 6 4 5 3 1;最后操作4,就变成了 1 3 5 4 6 2
输入
最多有10组数据,每个数据会包含两个整数n,m(1≤n,m<100,000), 接下来是m行数据,表示操作。
输出
对于每组数据,输出他们奇数位置的编号的和。
样例 #1
样例输入 #1
6 4
1 1 4
2 3 5
3 1 6
4
6 3
1 1 4
2 3 5
3 1 6
100000 1
4
样例输出 #1
Case 1: 12
Case 2: 9
Case 3: 2500050000
分析
移动和子,显然要大量操作元素的移动,根据这个特点,我们确定这道题使用链表来做,那使用双链表还是单链表呢?理论上都可以,但是使用单链表会遇到一个问题,当我们要操作题目中的插入到某个元素的左边或者右边的时候,我们还需要额外的遍历一遍(自己可以模拟一下操作,试一试更容易明白),所以使用双链表,我们可以很容易知道任意一个元素前后的情况.
我们使用两个数组来表示一个元素的左边和右边,例如
l
e
f
t
[
i
]
表示编号为
i
的盒子的左边的盒子的编号
left[i] 表示编号为 i 的盒子的左边的盒子的编号
left[i]表示编号为i的盒子的左边的盒子的编号
r
i
g
h
t
[
i
]
表示编号为
i
的盒子的右边的盒子的编号
right[i] 表示编号为 i 的盒子的右边的盒子的编号
right[i]表示编号为i的盒子的右边的盒子的编号
这样实现可以让我们的每一个操作都是的O(1)的时间复杂度.
当然还有一个翻转盒子序列的操作,但是仔细想一下,盒子正序和反序之后的差别是什么?一定要真的翻转吗?
盒子的序号被反转之后,1和2的操作变成原本的镜像操作,所以我们只需要设置一个bool值来指示我们盒子是否进行过翻转,进而我们使用不同的操作来达到我们的目的.
这里我在做的时候犯了一个巨大的错误,这个bug让我修了好长时间,如果使用传统的链表实现或许不会犯这样的错误,但是使用数组实现时,我只关注了一个元素的左和右,并没有考虑到当两个元素相邻时,如果我执行交换操作,结果就会发生错误.
下面是错误的实现方法(交换两个盒子):
void exchange(int x, int y){
int xr = _right[x];
int xl = _left[x];
int yr = _right[y];
int yl = _left[y];
_right[xl] = y;
_left[y] = xl;
_left[xr] = y;
_right[y] = xr;
_right[yl] = x;
_left[x] = yl;
_left[yr] = x;
_right[x] = yr;
#ifdef DEBUG
printChain();
#endif
}
上面的代码错就错在没有考虑两个盒子如果相邻的情况:
假设交换5和6这两个元素:
交换以后就会发现,会陷入死循环(遍历链表的时候).
下面是正确的实现
void exchange(int x, int y){
if(_left[x] == y){
remove(x);
insertToLeft(x, y);
return;
}else if(_right[x] == y){
remove(x);
insertToRight(x, y);
return;
}
int xr = _right[x];
int xl = _left[x];
int yr = _right[y];
int yl = _left[y];
_right[xl] = y;
_left[y] = xl;
_left[xr] = y;
_right[y] = xr;
_right[yl] = x;
_left[x] = yl;
_left[yr] = x;
_right[x] = yr;
}
其他的几个辅助函数就比较简单了
#include <iostream>
using namespace std;
//#define DEBUG
void remove(int x);
void insertToRight(int x, int y);
void insertToLeft(int x, int y);
void exchange(int x, int y);
void printChain();
void print();
int _left[100002];
int _right[100002];
int n = 0, m = 0;
bool rev = false;
int main(int argc, char **argv){
int op = 0;
int count = 1;
while(cin >> n >> m){
for(int i = 0; i <= n; i++){
_left[i] = i-1;
_right[i] = i+1;
}
#ifdef DEBUG
printChain();
#endif
while(m-- > 0){
cin >> op;
int x = 0;
int y = 0;
if(op == 1){
//将 X 移到 Y 的左边
cin >> x >> y;
if(rev){
if(_left[x] == y){
continue;
}
}else{
if(_right[x] == y){
continue;
}
}
remove(x);
if(rev){
insertToRight(x, y);
}else{
insertToLeft(x, y);
}
}else if(op == 2){
//将 X 移到 Y 的右边
cin >> x >> y;
if(rev){
if(_right[x] == y){
continue;
}
}else{
if(_left[x] == y){
continue;
}
}
remove(x);
if(rev){
insertToLeft(x, y);
}else{
insertToRight(x, y);
}
}else if(op == 3){
//将 X 与 Y 互换
cin >> x >> y;
exchange(x, y);
}else if(op == 4){
//翻转序列
rev = !rev;
}
}
unsigned int sum = 0;
int head = 0;
bool odd = rev ? (n % 2 == 0 ? false : true) : true;
for(int i = 0; i < n; i++){
sum += (odd ? _right[head] : 0);
head = _right[head];
odd = !odd;
}
cout << "Case " << count << ": " << sum << endl;
count++;
rev = false;
}
return 0;
}
void printChain(){
int head = 0;
for(int i = 0; i < n; i++){
head = _right[head];
cout << head << " ";
}
cout << endl;
}
void print(){
for(int i = 0; i <= n+1; i++){
cout << _left[i] << " ";
}
cout << endl;
for(int i = 0; i <= n+1; i++){
cout << _right[i] << " ";
}
cout << endl;
}
void remove(int x){
int l = _left[x];
int r = _right[x];
_right[l] = r;
_left[r] = l;
//#ifdef DEBUG
// printChain();
//#endif
}
void insertToRight(int x, int y){
int tmp = _right[y];
_right[y] = x;
_left[x] = y;
_right[x] = tmp;
_left[tmp] = x;
#ifdef DEBUG
printChain();
#endif
}
void insertToLeft(int x, int y){
int tmp = _left[y];
_left[y] = x;
_right[x] = y;
_left[x] = tmp;
_right[tmp] = x;
#ifdef DEBUG
printChain();
#endif
}
void exchange(int x, int y){
if(_left[x] == y){
remove(x);
insertToLeft(x, y);
return;
}else if(_right[x] == y){
remove(x);
insertToRight(x, y);
return;
}
int xr = _right[x];
int xl = _left[x];
int yr = _right[y];
int yl = _left[y];
_right[xl] = y;
_left[y] = xl;
_left[xr] = y;
_right[y] = xr;
_right[yl] = x;
_left[x] = yl;
_left[yr] = x;
_right[x] = yr;
#ifdef DEBUG
printChain();
#endif
}
所以,当我们的算法中有复杂度相对较高的操作时,我们不一定真的要操作,只要程序能告诉我们,确实执行了这个操作就是OK的.