题目大意:一共有n个盒子,编号1-n,共有m个操作,操作分为四种:
1、 1 b c操作:此操作会将b放在c的左边,使bc挨着,如果b已经在c的左边的话忽略。
2、 2 b c操作:此操作会将b放在c的右边,使bc挨着,如果b已经在c的右边的话忽略。
3、 3 b c操作:此操作会将b、c所在的位置翻转。
4、 4操作:此操作会将整个序列翻转
求在m个操作完成之后,所有奇数位置上的编号的和
我们很容易想到直接用两个数组暴力模拟操作,一开始我也是这么想的Σ(っ °Д °;)っ,但那样的话可能会到达O(10 * n^2)的级别,肯定会超时,所以在挣扎了半天后还是参考了紫书上的代码。
我们可以观察到,第四种操作如果是偶数个的话,那么我们就可以不用翻转整个序列,而如果是奇数个的话,那么必须要翻转,这样我们就可以优化很多了,所以我们可以用一个标记inv,让他初始为0,如果遇到4操作,那么将inv异或1,最后根据inv的值再决定翻不翻转即可。
但是这里有一个问题,就是4操作会影响1、2操作,因为我们是最后才进行翻转的。但是我们可以观察到,如果在操作是1或者是2且inv是1的时候(即需要翻转的时候)我们的操作可以变成3-a,这里a代表是1操作还是2操作。
那么为什么是3-a呢?
大家可以这么想,假设在暴力模拟的情况下,先翻转整个序列,再将b移动到c的左侧和先将b移动到c的右侧再翻转整个序列是不是一样的?
答案是一样的,大家可以推一下,也可以推出来4操作对3操作是没有影响的,因此我们就可以优化我们的代码了。
最后就是翻转了,那么我们真的有必要翻转吗?
其实是没有必要真的去进行翻转的。
假设需要翻转的话
如果n是奇数,那么我们就直接计算奇数位置的和即可,因为奇数的话奇数位置从前往后和从后往前都是一样的
但是如果n是偶数,奇数位置从前往后和从后往前是不一样的,我们可以利用等差数列求和公式计算出所有的和,再减去我们当前求出的奇数位置的和,就是结果
代码:
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
typedef long long LL;
int n , m;
int l[N] , r[N];//分别代表每个数的左边和右边是哪个数
inline void init()//初始化
{
for(int i = 0 ; i <= n ; ++ i)
{
l[i] = i - 1;
r[i] = (i + 1) % (n + 1);
}
l[0] = n;
}
void link(int b , int c)//连接函数
{
r[b] = c;
l[c] = b;
}
int main(void)
{
int ca = 1;
while(scanf("%d %d",&n , &m) != EOF)
{
init();
int inv = 0;
while(m --)
{
int a , b , c;
scanf("%d",&a);
if(a == 4) inv ^= 1;
else
{
scanf("%d %d",&b , &c);
if(a != 3 && inv) a = 3 - a;
else if(a == 3 && r[c] == b) swap(b , c);//要考虑b和c挨着的情况
if(a == 1 && b == l[c]) continue;
if(a == 2 && b == r[c]) continue;
int lb = l[b] , rb = r[b];
int lc = l[c] , rc = r[c];
if(a == 1){link(lb , rb); link(lc , b); link(b , c);}
else if(a == 2){link(lb , rb); link(b , rc); link(c , b);}
else if(a == 3 && r[b] == c){link(lb , c); link(c , b); link(b , rc);}
else if(a == 3 && r[b] != c){link(lb , c); link(c , rb); link(lc , b); link(b , rc);}
}
}
int idx = 0;
LL res = 0;
for(int i = 1 ; i <= n ; ++ i)
{
idx = r[idx];
if(i % 2) res += idx;
}
if(inv && n % 2 == 0) res = (LL)n * (n + 1) / 2 - res;
printf("Case %d: %lld\n",ca ++ , res);
}
return 0;
}