双向链表的使用

双向链表的使用

(本文仅为笔者学习,如有错误之处恳请各位读者指正)




简译:你有一行盒子,从左到右依次编号为1,2,3,...,n。可以执行一以下4种指令:

  • 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<=100000),以下m行每行包含一条指令。每组数据输出一行,即所有奇数位置的盒子编号之和。位置从左到右编号为1至n。


分析:

   采用双向链表,用left[i]和right[i]分别表示编号为i的盒子左边和右边的盒子编号(如果是0,表示不存在)。对于翻转操作,如果真的去翻转整条链则需要修改所有元素的指针。而实际上并不需要进行如此复杂的操作,在程序中增加一个标志位inv,表示有没有翻转操作(两次翻转可抵消:如果inv=1时又碰到一次4操作,则inv变为0)。

当碰到翻转操作时将inv置1,由于并没有对链表进行翻转,所以接下来当碰到1操作时需要转化为2操作才能在为翻转的链表上执行(对链表进行翻转再将X移动到Y的左边=将X移动到Y的右边再对链表进行翻转),同理在inv=1的前提下,碰到2操作需要将其转化为1操作再执行。最后根据inv的值进行不同的处理(inv=1时表示还需要将最后得到的链进行翻转,inv=0时则不需要)。


(以下程序只是在数组上模拟链表和指针的操作,而非真正意义上的链表和指针)

#include<cstdio>
#define sum(n) n*(n+1)/2  // 等差数列求和 
using namespace std;

const int maxn = 100000+5;
int left[maxn],right[maxn];  // 链表的左右指针数组 


// 连接两个节点 
void link(int L, int R){
	right[L] = R;  // 左结点的右指针指向右结点
	left[R] = L;   // 右结点的左指针指向左结点 
}


int main(){
	int n, m, 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);  // 取余n+1使最后一个元素指向头节点 
		}
		right[0] = 1;  // 头结点的右指针指向1号节点
		left[0] = n;   // 头结点的左指针指向尾结点 
		
		int op, X, Y, inv = 0;  // inv是否翻转 
		while(m--){
			scanf("%d", &op);
			if(op == 4)
			    inv = !inv;  // 翻转或两次翻转抵消 
			else{
				scanf("%d %d", &X, &Y);
				if(op!=3 && inv)
				    op = 3 - op;  // 翻转后的1操作相当于翻转前的2操作, 翻转后的2操作相当于翻转前的1操作
				if(op==1 && X==left[Y])  // X已经在Y左边 
				    continue; 
				if(op==2 && X==right[Y])  // X已经在Y右边
				    continue;
					
				int LX = left[X], RX = right[X], LY = left[Y], RY = right[Y];
				if(op == 1){  // 将X移动到Y左边 
					link(LX, RX);  // 移走X后,将X的左右两个节点互连
					link(LY, X);   // 将X插入到Y与Y的左结点之间:Y的左结点与X互连
					link(X, Y);    // XY互连 
				}else if(op == 2){
					link(LX, RX);  // 移走X后,将X的左右两个节点互连
					link(X, RY);   // 将X插入到Y与Y的右结点之间:X与Y的右结点互连
					link(Y, X);    // YX互连 
				}else if(op == 3){ // 交换XY节点 
					if(right[X] == Y){       // XY相连(Y在X右侧) 
						link(LX, Y); link(Y, X); link(X, RY);
					}else if(left[X] == Y){  // YX相连(Y在X左侧)
					    link(LY, X); link(X, Y); link(Y, RX); 
					}else{  // XY不相连 
						link(LX, Y); link(Y, RX); link(LY, X); link(X, RY); 
					}
				} 
			} 
		}
		int odd = 0;
		long long ans = 0;
		for(int i=1; i<=n; i++){
			odd = right[odd];  // 按照指针指向遍历链表
			if(i%2 == 1)
			    ans+=odd; 
		}
		
		// 盒子数为奇数时,翻转后原先位于奇数位置上的盒子仍然位于奇数位置上,直接输出ans 
		// 盒子数为偶数时,ans为翻转后偶数位置上的和。用总和sum(n)减去ans得到翻转后奇数位置上的和 
		if(inv && n%2==0)// 存在翻转&&盒子个数为偶数
		    ans = (long long)sum(n)-ans;  
	    printf("Case %d: %lld\n", ++kase, ans); 
	}
	return 0;
}
题目链接:Boxes in a Line - UVa 12657

  

       

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值