双向链表的使用
(本文仅为笔者学习,如有错误之处恳请各位读者指正)
简译:你有一行盒子,从左到右依次编号为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