luogu3960 列队
Posted headboy2002
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了luogu3960 列队相关的知识,希望对你有一定的参考价值。
题目大意
有一个方阵,在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达 这样的两条指令:
- 向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条 指令之后,空位在第 x 行第 m列。
- 向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条 指令之后,空位在第 n 行第 m列。
- 在每一个离队的学生要归队时,队伍中有且仅有第 n 行 第 m 列一个空位,这时这个学生会自然地填补到这个位置。
计算每一次离队事件中,离队的同学的编号是多少。n,m,q<=3*10^5。
题解
直接存储空间太大。经观察发现,一排中有很多同学的编号是连续的,因此我们可以用一个维护(beginId,lastId)的节点来表示多个同学的位置状态。因此我们可以对每一排(不包括一排中位于最右一列的位置)和最后一列各运用一棵SplayTree。
该SplayTree的初步定义
节点维护的是什么?
每个节点内的值Val(beginId,lastId)表示编号范围为[beginId,lastId]的同学的位置是连续的,这些同学(以后简称同学块)都由该节点维护。
树这个整体维护的是什么?
拿一排来说,这一排的Splay树内的所有节点所维护的同学块组成了这一排内的所有同学。
树节点排列的关键字是什么?
拿一排来说,关键字是每个节点所维护的同学块的在排中的位置。
出队入队过程
如果该同学不在最后一列,则在维护它所在排的SplayTree中输入同学的所在列,得到维护该同学的节点,然后将维护该同学的节点分成两半,一半维护原节点所维护同学块在该同学左面的那部分,一半维护右半部分。随后将最后一列同排的同学从维护最后一列的SplayTree中删除,将其加入到维护该排的SplayTree中的末尾,最后将出队的同学加入到维护最后一列的SplayTree中的末尾。如果同学本来就在最后一列,前半部分步骤就可以省掉了。
该SplayTree的具体实现
能维护关键字吗?
从我们对SplayTree操作的特殊性入手。如果我们给每个节点安排一个Key值,那么其含义应当是:其所维护的区间块中第一个同学所在的位置。但是只要从树中删除一个节点,以后节点的Key值就都要变化了。因此在本题中无法维护Key值。也就是说,决定这个树的排列顺序的只能是节点所维护的同学块间的相对位置,而不是绝对位置。所以我们无法GetNodeByKey。
那么如何由点的位置找到维护它的节点?
节点的新定义
在SplayTree的实现过程中,同学块的存在等价于:同学块内的每一个同学的编号都能映射到一个假想的值上,那么就相当于一个同学块内的同学在假想的值这方面都并列,在这个节点中假想的值重叠的个数Cnt便是这个节点所维护的同学块中同学的数量。因此,拿一排来说,把一位同学所在列数作为它在树中的排名,我们就可以通过FindKth来找到维护这位同学的节点。
没了关键字,树的排序方式怎么就能维护了?
我们发现往树中插入值时,总是从树的末尾插入。而用到Key值总是在树的任意一个地方插入节点时才需要。
如何拆点?
把左面的点保持原位,右面的点设为它的后继(如果原节点无右孩子,则设为右孩子,否则将GetNextNode()与右节点设为左父子)。(不要忘了设置cnt和size,且先设右面的点,再设左面的点,因为设置右面的点时可能需要用到左面的点的值)
#include <cstdio> #include <cstring> #include <algorithm> #include <cstdlib> #include <cassert> using namespace std; #define int long long const int MAX_ROW = 300010; int TotRow, TotCol; struct IdRange { int BeginId, LastId; IdRange(int beginId, int lastId) :BeginId(beginId), LastId(lastId) {} IdRange() {} }; template<class ValType> struct SplayTree { private: struct Node { ValType Val; Node *LeftSon, *RightSon, *Father; int Cnt, Size; Node(ValType val, int cnt, Node *father) :Val(val), Father(father), Cnt(cnt), Size(cnt), LeftSon(NULL), RightSon(NULL) {} bool IsRoot() { return Father == NULL || (Father->LeftSon != this && Father->RightSon != this); } bool IsLeftSon() { assert(!IsRoot()); return Father->LeftSon == this; } void Refresh() { Size = (LeftSon ? LeftSon->Size : 0) + (RightSon ? RightSon->Size : 0) + Cnt; } }; Node *Root; void SetRoot(Node *cur) { Root = cur; if (Root) Root->Father = NULL; } void Rotate(Node *cur) { Node *gfa = cur->Father->Father; Node **gfaSon = cur->Father->IsRoot() ? &Root : cur->Father->IsLeftSon() ? &gfa->LeftSon : &gfa->RightSon; Node **faSon = cur->IsLeftSon() ? &cur->Father->LeftSon : &cur->Father->RightSon; Node **curSon = cur->IsLeftSon() ? &cur->RightSon : &cur->LeftSon; *faSon = *curSon; if (*faSon) (*faSon)->Father = cur->Father; *curSon = cur->Father; (*curSon)->Father = cur; *gfaSon = cur; (*gfaSon)->Father = gfa; (*curSon)->Refresh(); cur->Refresh(); } void Splay(Node *cur) { while (!cur->IsRoot()) { if (!cur->Father->IsRoot()) Rotate(cur->IsLeftSon() == cur->Father->IsLeftSon() ? cur->Father : cur); Rotate(cur); } } pair<Node*, int> FindKth(Node *cur, int rank)//pair中int指rank对应数据有并列时,rank与并列最高排名的距离[1...cutCnt] { while (true) { assert(cur); int leftSize = cur->LeftSon ? cur->LeftSon->Size : 0, rootSize; if (rank <= leftSize) cur = cur->LeftSon; else if (rank <= (rootSize = leftSize + cur->Cnt)) return pair<Node*, int>(cur, rank - leftSize); else { cur = cur->RightSon; rank -= rootSize; } } } void InsertBack(Node *&root, ValType val, int cnt) { Node **cur = &root, *prev = root ? root->Father : NULL; while (*cur) { prev = *cur; cur = &(*cur)->RightSon; } *cur = new Node(val, cnt, prev); Splay(*cur); } Node *GetPrevNode(Node *cur) { cur = cur->LeftSon; if (!cur) return NULL; while (cur->RightSon) cur = cur->RightSon; return cur; } Node *GetNextNode(Node *cur) { cur = cur->RightSon; if (!cur) return NULL; while (cur->LeftSon) cur = cur->LeftSon; return cur; } void DeleteNode(Node *cur) { Splay(cur); if (!cur->LeftSon || !cur->RightSon) SetRoot(cur->LeftSon ? cur->LeftSon : cur->RightSon); else if (cur->LeftSon && cur->RightSon) { Node *NewRoot = GetPrevNode(cur); Splay(NewRoot); assert(NewRoot->RightSon == cur && cur->LeftSon == NULL); NewRoot->RightSon = cur->RightSon; if (NewRoot->RightSon) NewRoot->RightSon->Father = NewRoot; } } public: void InsertBack(ValType val, int cnt) { InsertBack(Root, val, cnt); } pair<ValType, int> FindKth(int rank)//pair中int指rank对应数据有并列时,rank与并列最高排名的距离[1...cutCnt] { pair<Node*, int> state = FindKth(Root, rank); Node *find = state.first; Splay(find); return pair<ValType, int>(find->Val, state.second); } void Split(int rank, int cutCnt, ValType*(*SplitVal)(ValType&, int), bool(*IsNull)(ValType&))//把并列的分为[1...curCnt-1]和[cutCnt+1...cnt]两部分 { pair<Node*, int> state = FindKth(Root, rank); Node *cur = state.first; ValType* a = SplitVal(cur->Val, cutCnt); if (IsNull(a[0]) && !IsNull(a[1])) { swap(a[0], a[1]); cutCnt = cur->Cnt - cutCnt + 1; } if (IsNull(a[0])) DeleteNode(cur); else { Node *newNode = NULL; if (!IsNull(a[1])) { Node *nextFa = GetNextNode(cur); if (nextFa == NULL) { newNode = new Node(a[1], cur->Cnt - cutCnt, cur); cur->RightSon = newNode; } else { newNode = new Node(a[1], cur->Cnt - cutCnt, nextFa); nextFa->LeftSon = newNode; } } cur->Val = a[0]; cur->Cnt = cutCnt - 1; Splay(newNode ? newNode : cur); } } }; SplayTree<IdRange> _trees[MAX_ROW]; void Init() { for (int row = 1; row <= TotRow; row++) _trees[row].InsertBack(IdRange((row - 1) * TotCol + 1, row * TotCol - 1), TotCol - 1); for (int row = 1; row <= TotRow; row++) _trees[0].InsertBack(IdRange(row * TotCol, row * TotCol), 1); } IdRange* SplitRange(IdRange& range, int cutLen) { IdRange* a = new IdRange[2]; a[0] = IdRange(range.BeginId, range.BeginId + cutLen - 2); a[1] = IdRange(range.BeginId + cutLen, range.LastId); return a; } bool IsNull(IdRange& range) { return range.BeginId > range.LastId; } int Solve(int row, int col) { if (col == TotCol) { //出队 pair<IdRange, int> out = _trees[0].FindKth(row); //向前看齐 _trees[0].Split(row, 1, SplitRange, IsNull); //入队 _trees[0].InsertBack(out.first, 1); return out.first.BeginId; } else { //出队 pair<IdRange, int> out = _trees[row].FindKth(col); //向左看齐 _trees[row].Split(col, out.second, SplitRange, IsNull); pair<IdRange, int> rowBack = _trees[0].FindKth(row); _trees[row].InsertBack(rowBack.first, 1); //向前看齐 _trees[0].Split(row, 1, SplitRange, IsNull); //入队 _trees[0].InsertBack(IdRange(out.first.BeginId + out.second - 1, out.first.BeginId + out.second - 1), 1); return out.first.BeginId + out.second - 1; } } #undef int int main() { int opCnt; scanf("%lld%lld%d", &TotRow, &TotCol, &opCnt); Init(); while (opCnt--) { long long row, col; scanf("%lld%lld", &row, &col); printf("%lld ", Solve(row, col)); } return 0; }
以上是关于luogu3960 列队的主要内容,如果未能解决你的问题,请参考以下文章