初见安~最近想搞搞数据结构,就翻到了这个树状数组的题。嘿嘿。这里是传送门:洛谷P3960 列队
听说有平衡树做法……有机会的话回来补上吧【现在太菜了还不会
题解(思路和代码基本上都是参考别人的题解)
先说一下看到题目过后的想法吧,应该是80分的写法。
手枚一下就可以发现,其实一个(x,y)的人离开再回来就相当于是把这一行和最后一列相应的部分移动一下,再把这个人放到最后一列的最后面。也就是说每次移动都只会涉及到该行和最后一列。所以最后一列肯定是要单独拿出来整的。
首先1~10有50分q都特别小,可以支撑。可以用链表模拟整个操作。
其次是11~14,都只有一行,也就是一维的问题了。扔出去,排到后面,这其实很像队列。为了保证复杂度,肯定不能模拟大家一起移动的情况了,那移开的位置就让它空着,回来的时候加一个位置。这样的话我们要找的数就从第y列的那一个变成了第y个。查找方式在前缀和上二分找即可,第一个前缀和为y的就是我们要找的第y个的位置。为了维护前缀和,我们可以用树状数组。
同理15~16,其实动的只有第一行和最后一列,把他俩当成一个拐了弯的横行,同11~14处理即可。
然后因为考虑到3e5个树状数组会炸就想不下去了……
接下来是正解。
每次移动可以看成三个动作,离开,移动,回来。离开只跟该行有关,移动和加回来只跟最后一列有关。所以可以分开,维护需要的信息即可。再来,为了避免3e5个树状数组,我们尝试把每一行也独立开来,用同一个树状数组。事实上是完全可以的,因为互不相干。那么清空还原呢?其实因为操作只有3e5次,我们每次把对树状数组改动的部分记录下来,挨个还原回去就可以了。还原操作整体复杂度。
对于每一行,如前文11~14的处理方式,我们预先把每一行实际要移开的那个数的位置找出来。如对于一行:1 2 3 4 5,我移动的坐标的y坐标是:1 3 4 ,移开后的数补到末尾,那么实际移动的就是1,4,1。依旧是用树状数组维护前缀和,然后二分查找。复杂度。
每行的离开的动作,因为这里我把因为移开而从最后一列加进来的数单独放到了一个vector里面,所以要和原本的那m个数分开,就要讨论一下情况……如果实际位置【二分后找到的位置y】<m,那直接就移开第m个;y=m,那就是移动的列上对应该行的那个;如果y>m,那就是在我新开的vector里面,位置为该行第y-m个。
移动和回去其实相当于是放一个最后一列的到这一行去,再把移开的那个加到最后一列后面。显然我们同样是树状数组维护前缀和二分找放哪一个去那一行,同理的维护。
emmmmm好像就这样了?看代码可能会好理解一些。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#define maxn 300005
//#define int long long
using namespace std;
typedef long long ll;
int read() {
int x = 0, f = 1, ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
return x * f;
}
int n, m, q;
struct node {int x, y, flag, id;} Q[maxn];
bool cmp(node a, node b) {return a.x == b.x? a.id < b.id : a.x < b.x;}
vector<node> line[maxn];
vector<int> tmp;
vector<ll> wt[maxn];
int t[maxn << 2];
int lowbit(int x) {return x & (-x);}
void add(int x, int y) {for(; x <= 6e5; x += lowbit(x)) t[x] += y;}
int sum(int x) {int res = 0; for(; x; x -= lowbit(x)) res += t[x]; return res;}
ll clm[maxn << 1];
signed main() {
n = read(), m = read(), q = read();
for(int i = 1; i <= q; i++) Q[i].x = read(), Q[i].y = read(), Q[i].id = i, Q[i].flag = false, line[Q[i].x].push_back(Q[i]);
for(int i = 1; i <= n; i++) sort(line[i].begin(), line[i].end(), cmp);
//line把每一行的操作分开
for(int i = 1; i <= 6e5; i++) t[i] = lowbit(i);//树状数组初始化,相当于每个位置都是1
for(int i = 1; i <= n; i++) {//预处理
tmp.clear(); register int len = m;
for(int j = 0; j < line[i].size(); j++) {
node now = line[i][j];
if(now.y == m) {Q[now.id].flag = true; continue;}//如果是第m个,方便判断加个flag
register int l = 1, r = len, mid;
while(l < r) {//二分找位置
mid = l + r >> 1;
if(sum(mid) >= now.y) r = mid;
else l = mid + 1;
}//l就是要移开的实际位置
add(l, -1); tmp.push_back(l); Q[now.id].y = l; len++;
}
for(int i = 0; i < tmp.size(); i++) add(tmp[i], 1);//tmp用于还原
}
register int len = n; ll ans;
for(int i = 1; i <= n; i++) clm[i] = 1ll * i * m;//clm是最后一列第i个位置的编号
for(int i = 1; i <= q; i++) {//执行每个操作
if(!Q[i].flag) {
if(Q[i].y < m) ans = 1ll * (1ll * (Q[i].x - 1) * m + Q[i].y);
else Q[i].y -= m, ans = wt[Q[i].x][Q[i].y];
}
register int l = 1, r = len, mid;
while(l < r) {//同理二分找是最后一列的第几个被扔进去了
mid = l + r >> 1;
if(sum(mid) >= Q[i].x) r = mid;
else l = mid + 1;
}
add(l, -1);
if(!Q[i].flag) wt[Q[i].x].push_back(clm[l]);
else ans = clm[l];
printf("%lld\n", ans);
clm[++len] = ans;//加入最后一列
}
return 0;
}
迎评:)
——End——