Boxes in a Line UVA——12657

也可以查看这里

题目链接

题目大意:一共有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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值