UVa12657移动盒子
当插入删除搬家移动位置比较多的时候,普通数组必定超时,此时在数据结构上考虑优化,用链表。
要知道盒子的前驱和后继,采用双向链表。
结点类三个成员,左指针、右指针、数据值。指针是数组下标。
因为该题目是根据数据值来移动,而不是数据值的位置,所以最好是把数据值就当作数组下标,直接将元素对应到房间下标。因此结点类就不需要3个成员,只需要2个。
此外,仍然用0号代表头结点,初始化first指针指向0号,头结点的右指针指向第一个实际元素,头结点的左指针指向最后一个元素。维护last指针指向最后一个元素,最后一个元素的左指针指向前驱,右指针指向头结点。
操作3——交换位置
画个图就知道如何写了,而且还要确定好执行顺序。
void SWAP(int a, int b) { // 交换位置
rlink[llink[a]] = b;
llink[rlink[a]] = b;
rlink[llink[b]] = a;
llink[rlink[b]] = a;
int t = rlink[b];
rlink[b] = rlink[a];
rlink[a] = t;
t = llink[b];
llink[b] = llink[a];
llink[a] = t;
return;
}
这种写法对于一般的情况是可以的,但是对于相邻的情况来说就会出现问题,就会出现自己的左右指针指向自己的情况。因此数据结构上的操作要考虑完整。以上代码交上去得了一个超时,幸好有一组测试数据对应到了超时的这种情况。
4 1
3 3 4
这组数据迟迟不能出结果。陷入了死循环,交换写得有问题。特殊情况的考虑要贯穿所有操作。
这两种写法不能合并。3种情况合成2种就是让链上位于左边的数在前面,视情况交换a和b。
if (llink[a] == b) {
int t = a;
a = b;
b = t;
}
!> 值突然改变要么就是越界要么就是==写成=。
不相邻情况:
a->llink->rlink=b
a->rlink->llink=b
b->rlink->llink=a
b->llink->rlink=a
// 三变量交换,这里是直观写法
a->rlink=b->rlink
b->rlink=a->rlink
// 三变量交换,这里是直观写法
a->llink=b->llink
b->llink=a->llink
相邻情况:
a->llink->rlink=b
b->rlink->rllink=a
a->rlink=b->rlink
b->llink=a->llink
b->rlink=a
a->llink=b
操作4——反转整条链
反转整条链,将last指针和头结点指针对调。但是这样还不够,2、3操作涉及把盒子移动到左边右边,如果反转了,左边右边关系就改变了,如果对每个结点对调左右指针会比较麻烦和费时。因此引入一个标记,一旦引入这个标记,其他操作都要考虑这个标记。线段树的懒标记也是引入标记。
提示6-6非常核心:
如果数据结构上的某一个操作很耗时,有时可以用加标记的方式处理,而不需要真的执行那个操作。但同时,该数据结构的所有其他操作都要考虑这个标记。
如果a在b左边,反转整条链,a就在b的右边。如果a要放到b的左边,反转整条链就是把a放到b的右边。
例子:1 a b
- 没有反转 执行前 a <-> c <-> d <-> b 执行后 c <-> d <-> a <-> b
- 有反转 执行前 b <-> d <-> c <-> a 执行后 a <-> b <-> d <-> c
再把这个执行结果反转回去就是c <-> d <-> b <-> a,相当于没有反转 2 a b
因此如果有反转,输出的时候也要反转。
求奇数位置的盒子编号就挨着走链,直到指回头结点。
如果是盒子总数是奇数,反转就不影响结果,如果盒子总数是偶数,反转就是取补。注意最大50亿超了int。注意修改数据类型则所有地方,包括输入输出都要改。涉及long long就要随时注意类型转换,还有防止溢出的问题。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100010;
int llink[MAXN];
int rlink[MAXN];
int first, last;
int inv; // 反转标记
int n, m;
void Init(int n) {
inv = 0;
first = 0;
last = n;
for(int i = 1; i < MAXN - 1; i++) { // 从1开始没有给0弄到
llink[i] = i - 1;
rlink[i] = i + 1;
}
rlink[0] = 1;
rlink[n] = 0;
return;
}
void SWAP(int a, int b) { // 交换位置
if (llink[a] == b || rlink[a] == b) {
//3种写法变成2种写法就是使得位于左边的数在前面,交换a和b。
if (llink[a] == b) {
int t = a;
a = b;
b = t;
}
rlink[llink[a]] = b;
llink[rlink[b]] = a;
rlink[a] = rlink[b];
llink[b] = llink[a];
rlink[b] = a;
llink[a] = b;
} else {
rlink[llink[a]] = b;
llink[rlink[a]] = b;
rlink[llink[b]] = a;
llink[rlink[b]] = a;
int t = rlink[b];
rlink[b] = rlink[a];
rlink[a] = t;
t = llink[b];
llink[b] = llink[a];
llink[a] = t;
}
return;
}
void InsertToLeft(int a, int b) { // 将a移动到b左边
rlink[llink[a]] = rlink[a];
llink[rlink[a]] = llink[a];
rlink[llink[b]] = a;
llink[a] = llink[b];
rlink[a] = b;
llink[b] = a;
return;
}
void InsertToRight(int a, int b) { // 将a移动到b右边
rlink[llink[a]] = rlink[a];
llink[rlink[a]] = llink[a];
llink[rlink[b]] = a;
rlink[a] = rlink[b];
llink[a] = b;
rlink[b] = a;
return;
}
int main() {
freopen("data.in", "r", stdin);
int k = 0;
while (scanf("%d%d", &n, &m) == 2) {
Init(n);
for (int i = 0; i < m; i++) {
int op = 0;
scanf("%d", &op);
if (op == 4) { // 反转整条链
if (inv == 1) {
inv = 0;
} else {
inv = 1;
}
} else if (op == 3) { // 交换位置
int a, b;
scanf("%d%d", &a, &b);
SWAP(a, b);
} else { // 移动到左边或者右边
if (inv == 1) {
op = 3 - op;
}
int a, b;
scanf("%d%d", &a, &b);
if (op == 1) { // 将a移动到b左边
if (llink[b] == a) {
continue;
}
InsertToLeft(a, b);
} else {
if (rlink[b] == a) {
continue;
}
InsertToRight(a, b); // 将a移动到b右边
}
}
}
long long ans = 0; // 最大的50亿超了int
int cur = rlink[0]; // 指向第一个元素
int i;
for (i = 1; cur != 0; i++) {
if (i % 2 == 1) {
ans += cur;
}
cur = rlink[cur];
}
if (inv == 1 && n % 2 == 0) {
ans = (long long)(n + 1) * (n / 2) - ans;
}
printf("Case %d: %lld\n", ++k, ans);
}
return 0;
}