UVA12657 Boxes in a Line

移动盒子 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的.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值