紫书刷题进行中,题解系列【GitHub|CSDN】
例题6-5 UVA12657 Boxes in a Line(33行AC代码)
题目大意
对升序排列的n个数字(1开始编号)进行一系列操作,输出最终序列的奇位数之和。操作如下:
1 X Y
:把X移到Y左侧2 X Y
:把X移到Y右侧3 X Y
:交换X和Y4
:反转序列(逆置)
思路分析
若是直接用链表模拟,在查找和反转时会消耗大量时间,导致超时,因此,如何解决这两个问题是关键。
- 查找:为了简化,可用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保存,否则会溢出
AC代码(C++11,链表,反转标记优化)
#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;
}