三维偏序cdq:题目中明显说了,三个维度(a, b, c)
这里有必要说明,在一些二维cdq的题目中有题中指定的操作的时间作为第一个维度,这是容易看的,因为对于操作对于后续操作的影响必定要让时间早的操作优先执行,这样才能够保证方法的准确性,相当于弱化了第一个维度存在的含义。但是这里就是要把第一个维度与其他维度等价考虑。
这个题目有细节地方的考虑,下面再说,这边先说概括性的思路。
有三个维度,我们知道cdq函数在递归调用返回之后,左边一半的区间和右边一半的区间需要有一个维度是所有的右值都大于(小于也行)左边的值的对应维度,(这个在二维cdq中的体现就是时间的顺序), 那么对于这道题我们可以排第一个维度a 的顺序, 那么对于左边和右边的区间, 双指针可以用在b维度上,效果就是如果左边的b值比右值的b值小于或者等于,那么已知左边的a值小于右边的a值,这样就是在忽略c值的基础上已经满足题意,那么c值怎么考虑呢?,权值树状数组,把左值a比右值a小,同时左值b比右值b小的左值的c 加在权值树状数组中,如果遇到左值的b开始大于右值的b,那么说明左边可能的c值全部放在树状数组中了,先拿当前右值的c去树状数组中查询查到前缀中有几个值就是满足题意的点的个数了。
这个题的细节:
- 要考虑多个点在同一个位置的情况,这个怎么处理呢?先去重,统计这个值位置有几个点作为权值,那么加入树状数组的时候是按照下标加入当前的右值的权值。
- 权值线段树在每一个递归的函数中都要从全部空开始用, 但是用memset应该会超时, 因为递归的层数会让这个时间变大很多很多,所以每一次加了什么值,加在什么位置都要记录下来,用完之后用这些存下来的记录加上赋值即可达到清空的作用。
我是按照坐标自己也可以算作自己的一个满足题意的解做的,在输出的时候就从1开始,所以结果与题目中的意思是等价的。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define min(i, j) (i < j ? i : j)
#define max(i, j) (i > j ? i : j)
const int maxn = 2e6 + 10;
struct BITree
{
private:
ll savedata[maxn];
int n;
inline int lowbit(int pos) { return pos & (-pos); }
public:
inline void init(int n1)
{
n = n1;
memset(savedata, 0, sizeof(savedata));
}
inline void add(int pos, ll val)
{
while (pos <= n)
{
savedata[pos] += val;
pos += lowbit(pos);
}
}
inline ll sum(int pos)
{
ll res = 0;
while (pos > 0)
{
res += savedata[pos];
pos -= lowbit(pos);
}
return res;
}
} tr;
struct node
{
int id;
int a, b, c;
int cnt;
bool operator<(const node &n) const
{
if (a != n.a)
return a < n.a;
else if (b != n.b)
return b < n.b;
else
{
return c < n.c;
}
}
bool operator==(const node &n) const
{
return a == n.a && b == n.b && c == n.c;
}
} saveori[maxn], temp[maxn];
ll ans[maxn];
ll ans1[maxn];
void cdq(int l, int r)
{
if (l == r)
{
ans[saveori[l].id] += saveori[l].cnt;
return;
}
int mid = (l + r) / 2;
cdq(l, mid);
cdq(mid + 1, r);
int larr = l;
int rarr = mid + 1;
vector<pair<int, int>> se;
//左边的a全部比右边的a小, 对于每一边,b的值单调递增, c的值不确定
while (larr <= mid && rarr <= r)
{
node &lo = saveori[larr];
node &ro = saveori[rarr];
if (lo.b <= ro.b)
{
tr.add(lo.c, lo.cnt);
se.push_back({lo.c, lo.cnt});
larr++;
}
else
{
ans[ro.id] += tr.sum(ro.c);
rarr++;
}
}
while (rarr <= r)
{
node &ro = saveori[rarr++];
ans[ro.id] += tr.sum(ro.c);
}
//原则上来说树状数组每次必须全部清空,但是如果memset那么乘上递归的次数,应该会超时。
//所以把前面加入的位置和值都存起来,这里加上对应的负值即可达到清空的目的
for (pair<int, int> v : se)
{
tr.add(v.first, -v.second);
}
larr = l;
rarr = mid + 1;
int tempid = l;
while (larr <= mid && rarr <= r)
{
node &lo = saveori[larr];
node &ro = saveori[rarr];
if (lo.b < ro.b)
{
temp[tempid++] = lo;
larr++;
}
else if (lo.b == ro.b)
{
if (lo.c < ro.c)
{
temp[tempid++] = lo;
larr++;
}
else
{
temp[tempid++] = ro;
rarr++;
}
}
else
{
temp[tempid++] = ro;
rarr++;
}
}
while (larr <= mid)
{
temp[tempid++] = saveori[larr++];
}
while (rarr <= r)
{
temp[tempid++] = saveori[rarr++];
}
for (int i = l; i <= r; i++)
{
saveori[i] = temp[i];
}
}
int main()
{
// freopen("in.txt", "r", stdin);
int n, k;
cin >> n >> k;
tr.init(k + 90);
for (int i = 1; i <= n; i++)
{
int a, b, c;
cin >> a >> b >> c;
saveori[i].a = a;
saveori[i].b = b;
saveori[i].c = c;
saveori[i].id = i;
saveori[i].cnt = 1;
}
sort(saveori + 1, saveori + 1 + n);
int larr = 1;
int rarr = 2;
while (rarr <= n)
{
if (saveori[larr] == saveori[rarr])
{
saveori[larr].cnt++;
rarr++;
}
else
{
larr++;
saveori[larr] = saveori[rarr];
rarr++;
}
}
cdq(1, larr);
for (int i = 1; i <= larr; i++)
{
ans1[ans[saveori[i].id]] += saveori[i].cnt;
}
for (int i = 1; i <= n; i++)
{
cout << ans1[i] << endl;
}
}