【题目链接】UVA-12657 Boxes in a Line
【样例】
【题意】
给定N个盒子,依次标号为1~N,有下面4种操作:
1 X Y 表示将X移到Y的左边;
2 X Y 表示将Y移到Y的右边;
3 X Y 表示交换X与Y的位置;
4 表示将1~N所有的盒子反序。
要你求经过M次操作之后,所有奇数位置的盒子标号之和。
【分析】
首先考虑第4种操作:
可以发现:
当N为奇数的时候,将所有盒子逆序,此时所有奇数位置的盒子标号之和是不会改变的;
当N是偶数的时候,所有奇数位置的盒子标号之和=所有盒子标号之和-所有偶数位置的盒子标号之和,因此当需要逆序的时候我们可以不执行逆序,转而计算此时所有偶数位置的盒子标号之和,即所有盒子标号-奇数位置标号之和。不需要逆序的时候则正常计算即可。
因此我们可以统计操作4的次数而不执行。
>如果数据结构中的某一项操作很耗时,有时我们可以用加标记的方式来处理,而不必真的去执行该项操作,但同时,该数据结构的其他所有操作都需要考虑该项操作。
如:若在当前操作前出现了奇数倍的操作4,那么接下来进行操作1,2的时候,需要将操作反向,即操作1变为2,2变为1。
输入如下时:
N=6------> 1 2 3 4 5 6
4------> 6 5 4 3 2 1
1 1 4------>6 5 1 4 3 2
上面的情况中求得的答案等价于下面这种情况:
N=6------> 1 2 3 4 5 6
2 1 4------> 2 3 4 1 5 6
其他细节见代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100000+5;
int N,M;
int L[maxn],R[maxn];
int link(int a,int b){
//辅助函数是两个节点相互链接
R[a]=b;L[b]=a;
return 0;
}
int main(){
int i,k;
int O,X,Y;
int cnt=0;
while(scanf("%d%d",&N,&M)==2){
cnt++;
int cur=0,ct=0;
for(i=0;i<N;i++)
R[i]=(i+1)%(N+1);
for(i=N;i>0;i--)
L[i]=i-1;
R[N]=0;L[0]=N;
//以上初始化,N与0相链接
for(i=0; i<M; i++){
scanf("%d",&O);
if(O==4){
ct++;
continue;
}
else{
scanf("%d%d",&X,&Y);
if(O==3&&R[Y]==X)
swap(X,Y);
//交换后使得Y保证在X的右边,利于后面的统一交换操作
if(ct%2&&O!=3){
O=3-O;
}//奇数次逆序,操作反向
int LX=L[X],RX=R[X],LY=L[Y],RY=R[Y];
//这一步至关重要,倘若不使用这一步,在连接节点的过程中
//原始值会发生改变,导致之后系列的连接出错
//考虑同样效果的写法:
//if(O==1){
//int xl=L[X],xr=R[X];
//R[xl]=xr,L[xr]=xl;
//int yl=L[Y];
//R[yl]=X,L[X]=yl;
//L[Y]=X,R[X]=Y;
//}
if(O==1&&LY!=X){
link(LY,X);
link(LX,RX);
link(X,Y);
continue;
}
if(O==2&&RY!=X){
link(X,RY);
link(Y,X);
link(LX,RX);
continue;
}
if(O==3){
//处理操作3时注意细节,考虑XY相邻的两种情况
if(RX==Y){
link(LX,Y);link(Y,X);link(X,RY);
}
else{
link(LX,Y);link(Y,RX);link(LY,X);link(X,RY);
}
continue;
}
}
}
int s=0;
long long ans=0;
for(i=1; i<=N; i++){
s=R[s];
if(i%2)
ans+=s;
}
if(ct%2&&N%2==0)
ans=(long long)N*(N+1)/2-ans;
//当N为偶数且有奇数次逆序时,可以不逆序而计算此时的偶数位置标号之和
cout<<"Case "<<cnt<<": "<<ans<<endl;
}
return 0;
}