UVA 12657 移动盒子 最详细讲解~

题目链接

题目大意:

有一行从左到右编号为 1,2 ,3,4……n 的盒子,定义以下四种指令:

① 1 X Y :将编号为X 的盒子移动到 Y 的左边(若X已在 Y 的左边,则忽略此指令)
② 2 X Y : 将编号为 X 的盒子移动到 Y 的右边 (若X 已在Y的右边,则忽略此指令)
③ 3 X Y :交换 X 和 Y 盒子的位置
④ 4 : 将所有的盒子逆序

输入:

输入不超过10组数据,对于每组数据,第一行输入两个数字,分别为盒子的个数 n 以及 指令的数量 m,接下来的 m 行 给出 m 条指令。

输出:

完成指令后的在 奇数位置(从左往右)的盒子的编号和 。

思路分析:

  1. 直接模拟 指令操作即可,但是不能用数组来保存盒子,会超时。
  2. 盒子的移动 不仅涉及到给定的 X 和 Y ,还有X 和 Y 周围的盒子,所以,应使用 双向链表 而不是 单向链表 的形式来存储盒子,双向链表可以使用双指针实现,但是也可以采用两个位置数组 Left 和 Right 实现,并且后者效率更高,因为它可以像数组一样使用下标来直接定位。
  3. 可以使用以下的方法来实现双链表两个结点的链接:
void link(int L , int R ) 
{
	Right[L] = R ; Left[R] = L;
}
  1. 对于第四个指令,即逆序操作,我们不需要真的将其逆序,我们只要设定一个标记 flag ,flag 初始化为 0 ,表示并未执行过 逆序,而后每当执行一次 逆序指令,我们让 flag = ! flag,最后,我们根据flag 的值,如果是 1 ,说明 最终是需要逆序的,如果flag 是 0,那么说明没有执行过 逆序指令或者 执行了偶数个逆序指令。

PS:如果采用标记符的方式来处理逆序指令,那么执行其他的指令时,可能需要根据 flag 的状态来进行一定的调整。

  1. 对于双向链表,我们可以将其头尾相连,形成一个环结构,从而可以保证后续操作的统一性,而不用去专门判断是否为 头节点 和 尾节点。

6 .对于 指令3 ,即交换指令,它不受flag 的影响,但是交换的过程却和 X 与 Y 的相对位置有关:

  • 如果 X 和 Y 不相邻,那么 可以 通过以下步骤完成交换:
    link(LX,Y),link(Y,RX),link(LY,X),link(X,RY);
  • 如果X 和 Y 相邻,那么又可以分成两种情况:
    1) X 在 Y 的左边:link(LX,Y),link(Y,X),link(X,RY)。
    2)Y 在 X 的左边:link(LY,X ),link(X,Y),link(Y,RX)。
    观察以上两种情况,我们可以预处理X,Y,使得 X 始终在 Y的左边,具体而言就是当发现Y 在 X 的左邻时,交换Y 和 X 即可,这样,情况就统一为 1)。

最终代码如下:

#include <iostream>

using namespace std;
int N,M,kase;
const int maxn = 100000 + 10;
int Left[maxn],Right[maxn];
void link(int L,int R) 
{
	Right[L] = R;
	Left[R] = L;
}
int main()
{
	kase = 0;
	while(scanf("%d %d", &N ,&M)==2) {
		for(int i = 1; i <= N; ++i) {
			Left[i] = i-1;
			Right[i] = (i+1)%(N+1);
		}
		Right[0] = 1 , Left[0] = N;
		int op, x, y;
		bool flag = false;
		while(M--) {
			scanf("%d",&op);
			if(op==4) {
				flag = !flag;
				continue;
			}
			scanf("%d %d", &x, &y);
			if(op==3 && Right[y]==x) swap(x,y);
			if(op!=3 && flag) op = 3 - op;	// 逆序下,左移指令实际是右移,右移指令实际是左移指令。
			if(op==1 && x == Left[y]) continue;
			if(op==2 && x == Right[y]) continue;
			
			int Lx = Left[x], Ly = Left[y], Rx = Right[x], Ry = Right[y]; 
			if(op==1) {
				link(Lx,Rx);link(Ly,x); link(x,y);
			} else if(op==2) {
				link(Lx,Rx); link(y,x); link(x,Ry); 
			} else {
				if(x==Left[y]) {link(Lx,y);link(y,x);link(x,Ry);}
				else {link(Lx,y);link(y,Rx); link(Ly,x); link(x,Ry);}
			}
		}
		long long ans = 0;	// int 可能会溢出
		int s = 0;
		for(int i = 1; i <= N; ++i) {
			s = Right[s];
			if(i%2==1) ans += s;
		} 
		// cout << ans << endl;
		if(flag && N%2==0) ans = (long long )N*(N+1)/2 - ans;	// 逆序后的奇数是偶数,偶数是奇数
		printf("Case %d: %lld\n",++kase,ans); 
	}
} 
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值