题目大意:
有一行从左到右编号为 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 条指令。
输出:
完成指令后的在 奇数位置(从左往右)的盒子的编号和 。
思路分析:
- 直接模拟 指令操作即可,但是不能用数组来保存盒子,会超时。
- 盒子的移动 不仅涉及到给定的 X 和 Y ,还有X 和 Y 周围的盒子,所以,应使用 双向链表 而不是 单向链表 的形式来存储盒子,双向链表可以使用双指针实现,但是也可以采用两个位置数组 Left 和 Right 实现,并且后者效率更高,因为它可以像数组一样使用下标来直接定位。
- 可以使用以下的方法来实现双链表两个结点的链接:
void link(int L , int R )
{
Right[L] = R ; Left[R] = L;
}
- 对于第四个指令,即逆序操作,我们不需要真的将其逆序,我们只要设定一个标记 flag ,flag 初始化为 0 ,表示并未执行过 逆序,而后每当执行一次 逆序指令,我们让 flag = ! flag,最后,我们根据flag 的值,如果是 1 ,说明 最终是需要逆序的,如果flag 是 0,那么说明没有执行过 逆序指令或者 执行了偶数个逆序指令。
PS:如果采用标记符的方式来处理逆序指令,那么执行其他的指令时,可能需要根据 flag 的状态来进行一定的调整。
- 对于双向链表,我们可以将其头尾相连,形成一个环结构,从而可以保证后续操作的统一性,而不用去专门判断是否为 头节点 和 尾节点。
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);
}
}