题目链接:https://vjudge.net/problem/UVA-12657
题意:你有一个盒子,从左到右编号为1,2,3,...,n。可执行下列四种操作:
1 X Y 表示把盒子X移动到盒子Y的左边(如果X已经在Y左边则忽略此指令)。
2 X Y 表示把盒子X移动到盒子Y的右边(如果X已经在Y右边则忽略此指令)。
3 X Y 表示交换盒子X Y的位置。
4 反转整条链。
输入多组数据,每组第一行为盒子数n和指令条数m(1<=n,m<=100000),以下m行每行包含一条指令。每组输出一行,输出所有奇数位置编号之和。位置从左到右编号n。
分析:用数组保存一定会超时。本题采用双向链表解决,用left[i]和right[i]分别表示编号为i的盒子左边和右边的编号(0表示不存在)。在这里,较难处理的是第四种操作:反转整条链,当遇到第四种操作时,若直接将链反转,反转一次就要改动left[i],right[i]所有元素指针,时间复杂度也是很高的,也必然会超时。在这里有一个小小的规律可循,试想一下,假如遇到操作四我们先不做任何处理,而是设一个变量记录此时链是否反转的状态,最后根据这个状态选择输出序列的奇数和还是偶数和,操作四处理起来就简单了。但是是否反转这个状态对操作一和操作二是有影响的,如果此时状态为反转,对操作一(二)的操作就相当于暂时忽略反转并进行操作二(一)。例如:原序列为1,2,3,4,5并且此时状态为反转,如果要把盒子2放到4左边,则操作后序列为:5,2,4,3,1;那么相当于在不反转而只是记录状态时把盒子2放到了盒子4 的右边。1,3,4,2,5(此时状态为反转,倒着读就是反转后的结果)。反转对操作三是没有影响的,即无论反转多少次,操作三中需要交换位置的两个盒子相对于其它盒子的位置都要发变化。最后如果状态为反转,输出偶数位置的和,否则输出奇数位置的和。
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN=100005;
int l[MAXN],r[MAXN],n,m;
void init() {
for(int i=1; i<=n; i++) {
r[i]=(i+1)%(n+1);
l[i]=i-1;
}
l[0]=n;r[0]=1;
}
void link(int a,int b) {
r[a]=b;
l[b]=a;
}
int main() {
//freopen("in.txt", "r", stdin);
int cas=1;
while(cin>>n>>m) {
init();
int op,flag=0;
while(m--) {
cin>>op;
if(op==4) flag=!flag;
else {
int x,y;
cin>>x>>y;
if(op==3&&r[y]==x) swap(x,y);//两元素相邻预处理
if(flag&&op!=3) op=3-op;//如果状态为反转,则操作一(二)转化为操作二(一)
if(op==1&&l[y]==x) continue;//忽略操作
else if(op==2&&r[y]==x) continue;//忽略操作
//链表更新操作
int lx=l[x],rx=r[x],ly=l[y],ry=r[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(r[x]==y) {
link(lx,y),link(y,x),link(x,ry);//特殊情况处理:即交换操作时两元素相邻时
} else {
link(lx,y),link(y,rx),link(ly,x),link(x,ry);
}
}
}
}
int k=0;
long long ans=0;
for(int i=r[0]; i!=0; i=r[i]) {
if(k%2==0) ans+=i;
k++;
}
if(flag&&n%2==0) ans=(long long)n*(n+1)/2-ans;//状态为反转时输出偶数位置和
cout<<"Case "<<cas++<<": "<<ans<<endl;
}
return 0;
}