33行代码AC——例题6-5 移动盒子(Boxes in a Line, UVa 12657)——解题报告

题目大意

对升序排列的n个数字(1开始编号)进行一系列操作,输出最终序列的奇位数之和。操作如下:

  • 1 X Y:把X移到Y左侧
  • 2 X Y:把X移到Y右侧
  • 3 X Y:交换X和Y
  • 4:反转序列(逆置)

思路分析

若是直接用链表模拟,在查找和反转时会消耗大量时间,导致超时,因此,如何解决这两个问题是关键。

  • 查找:为了简化,可用STL的list模拟链表,同时定义pos存储每个数对应的链表指针,在查询时时间复杂度为O(1)
vector<list<int>::iterator> pos(n+1);
  • 反转:不一定真正去反转链表,可定义开关标记inv=0,其中inv=1表示反转一次,再次反转则inv=0,由于增加了标记,那么每个操作均需考虑标记的值,当inv=1时,考虑如下情况:
    • 当op=1或2时,则需做相反操作,即op=3-op
    • 当op=3时,交换不受影响
    • 当op=4时,inv=0
    • 当输出时,若链表个数为偶数,则输出下标为偶数的元素和;否则均输出奇数下标的元素之和

注意点

  • 最后计算总和时需用long long保存,否则会溢出

代码

#include<bits/stdc++.h>
using namespace std;
int n, m, op, a, b, num=0;
int main() {
    while (scanf("%d %d", &n, &m) == 2) {
        list<int> l(n); // 存储1-n
        vector<list<int>::iterator> pos(n+1); // pos[i]表示数字i在list中的指针
        int idx=1, inv=0;
        for (auto p=l.begin(); p != l.end(); p++, idx++) { // 初始化
            *p = idx;
            pos[idx] = p;
        }
        for (int i = 0; i < m; i ++) { // m个操作
            scanf("%d", &op);
            if (op != 4) scanf("%d %d", &a, &b);
            if (op == 4) inv = !inv; // 反转标记
            else if (op == 3) swap(*pos[a],*pos[b]), swap(pos[a], pos[b]); // 交换
            else {
                l.erase(pos[a]); // 先擦除
                if (inv == 1) op = 3 - op; // 反转则左右交换
                auto p=pos[b];
                if (op == 2) p ++; // 插入右侧
                pos[a] = l.insert(p, a); // a的新位置
            }
        }
        long long cnt=1, oddsum=0; // 避免溢出
        for (auto p=l.begin(); p != l.end(); p++, cnt++) {
            if (cnt % 2 == 1) oddsum += *p;
        }
        printf("Case %d: %lld\n", ++num, (inv == 1 && n%2 == 0) ?  (long long)n*(n+1)/2-oddsum : (long long)oddsum);
    }
    return 0;
}

收获:

如果数据结构上的某一个操作很耗时,有时可以用加标记的方式处理,而不需要真的执行那个操作。但同时,该数据结构的所有其他操作都要考虑这个标记。


择苦而安,择做而乐,虚拟现实终究比不过真实精彩之万一。

发布了97 篇原创文章 · 获赞 104 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43899069/article/details/104845207
今日推荐