用两种线段树思路打开双元贡献计算
//二元查找,首先对长度排序,直接对数组二分找到长度区间,但是宽度是乱序的,要怎么保证?记住每次是查询长度区间1~x,如果能对这段区间的宽度经行长度查找就good了,那么直接建立no{l,r,vector——宽度》},pushup来合并区间,查到区间后就再来一次二分就行(分三个颜色线段树就好了)
//函数:
(1) tree[c][u].sorted_w.resize(lw.size() + rw.size());
merge(lw.begin(), lw.end(), rw.begin(), rw.end(), tree[c][u].sorted_w.begin());
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用64位整型防止溢出
const int MOD = 1e9+7; // 题目要求的模数
const int N = 1e5+5; // 最大数据范围
// 矩形结构体:记录长度l、宽度w、颜色c
struct Rect {
int l, w, c;
// 按长度升序排序,用于预处理排序
bool operator<(const Rect& o) const { return l < o.l; }
};
// 线段树节点结构体
struct Node {
int l, r; // 当前节点管理的区间范围 [l, r]
vector<int> sorted_w;// 存储该区间内所有宽度值的有序列表
} tree[3][N*4]; // 三棵线段树分别对应三种颜色(0红、1黄、2蓝)
vector<Rect> colors[3]; // 按颜色分组存储所有矩形
vector<int> sorted_l[3]; // 各颜色组的长度数组(已排序)
// 构建线段树
// 参数:c-颜色编号,u-当前节点编号,l/r-当前区间范围
void build(int c, int u, int l, int r) {
tree[c][u].l = l;
tree[c][u].r = r;
// 叶子节点:直接存储单个宽度值
if(l == r) {
tree[c][u].sorted_w = {colors[c][l].w};
return;
}
// 非叶子节点:递归构建左右子树
int mid = (l + r) >> 1;
build(c, u<<1, l, mid); // 左子树
build(c, u<<1|1, mid+1, r); // 右子树
// 合并左右子树的sorted_w数组,保持有序性
auto& lw = tree[c][u<<1].sorted_w;
auto& rw = tree[c][u<<1|1].sorted_w;
tree[c][u].sorted_w.resize(lw.size() + rw.size());
merge(lw.begin(), lw.end(), rw.begin(), rw.end(), tree[c][u].sorted_w.begin());
}
// 线段树区间查询:统计在区间[L,R]内宽度>target_w的数量
// 参数:c-颜色编号,u-当前节点,L/R-目标查询区间,target_w-目标宽度
int query(int c, int u, int L, int R, int target_w) {
// 当前节点区间与查询区间无交集
if(tree[c][u].r < L || tree[c][u].l > R) return 0;
// 当前节点区间完全包含在查询区间内
if(L <= tree[c][u].l && tree[c][u].r <= R) {
auto& vec = tree[c][u].sorted_w;
// 使用upper_bound找到第一个>target_w的位置
return vec.end() - upper_bound(vec.begin(), vec.end(), target_w);
}
// 递归查询左右子树
return query(c, u<<1, L, R, target_w) +
query(c, u<<1|1, L, R, target_w);
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0);
int n; cin >> n;
// 数据读取阶段:按颜色分组存储
for(int i = 0; i < n; ++i) {
int l, w, c;
cin >> l >> w >> c;
colors[c].push_back({l, w, c}); // 按颜色存入对应数组
}
// 预处理阶段:每个颜色组分别处理
for(int c = 0; c < 3; ++c) {
if(colors[c].empty()) continue;
// 步骤1:按长度升序排序该颜色组的所有矩形
sort(colors[c].begin(), colors[c].end());
// 步骤2:提取排序后的长度数组(用于后续二分查找)
sorted_l[c].reserve(colors[c].size());
for(auto& rect : colors[c]) {
sorted_l[c].push_back(rect.l);
}
// 步骤3:构建该颜色组的线段树(管理宽度值)
build(c, 1, 0, colors[c].size()-1);
}
int ans = 0;
// 遍历所有颜色组
for(int cur_c = 0; cur_c < 3; ++cur_c) {
// 遍历当前颜色组的每个矩形
for(auto& rect : colors[cur_c]) {
// 检查另外两种颜色组(必须不同颜色)
for(int other_c = 0; other_c < 3; ++other_c) {
if(other_c == cur_c) continue; // 跳过同色
if(colors[other_c].empty()) continue; // 空组跳过
// 在other_c颜色组中查找满足条件的矩形:
// 条件1:长度 < 当前矩形的长度(rect.l)
auto& sl = sorted_l[other_c];
int k = lower_bound(sl.begin(), sl.end(), rect.l) - sl.begin();
if(k == 0) continue; // 没有比当前长度小的
// 条件2:宽度 > 当前矩形的宽度(rect.w)
// 查询区间[0, k-1]内宽度>rect.w的数量
ans += query(other_c, 1, 0, k-1, rect.w);
ans %= MOD; // 及时取模防止溢出
}
}
}
cout << ans % MOD << "\n";
}
//还有一种动态更新操作,struct no{w的上下限,和个数}首先仍然是对长度经行排序,随着长度的大小从小到大插入,边插边插线,在范围内查找个数,对于长度相同的要延迟更新保证严格大于
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ls(u) (u<<1)
#define rs(u) (u<<1|1)
typedef pair<int,int> pii;
const int N=1e5+10, mod=1e9+7;
struct Node {
int l, r, sum;
} tr[N<<2][3]; // 三棵线段树,对应三种颜色(0红,1黄,2蓝)
short oth[3][2] = {{1,2}, {0,2}, {0,1}};
int n;
struct Rect {
int x, y, c;
bool operator<(const Rect& t) const { return x < t.x; }
} a[N];
void pushup(int c, int u) {
tr[u][c].sum = (tr[ls(u)][c].sum + tr[rs(u)][c].sum) % mod;
}
// 线段树更新操作:在颜色c的线段树u节点插入位置pos
void update(int c, int u, int l, int r, int pos) {
// 如果当前节点是叶子节点,直接增加计数
if(tr[u][c].l == tr[u][c].r) {
tr[u][c].sum = (tr[u][c].sum + 1) % mod;
return;
}
int mid = (tr[u][c].l + tr[u][c].r) >> 1;
if(pos <= mid) {
if(!tr[ls(u)][c].sum) {
tr[ls(u)][c] = {tr[u][c].l, mid, 0};
}
update(c, ls(u), l, mid, pos);
} else {
if(!tr[rs(u)][c].sum) {
tr[rs(u)][c] = {mid+1, tr[u][c].r, 0};
}
update(c, rs(u), mid+1, r, pos);
}
pushup(c, u);
}
// 线段树查询操作:统计颜色c的线段树u节点在[ql, qr]区间的和
int query(int c, int u, int ql, int qr) {
// 当前节点区间与查询区间无交集
if(tr[u][c].r < ql || tr[u][c].l > qr) return 0;
// 当前节点区间完全包含在查询区间内
if(ql <= tr[u][c].l && tr[u][c].r <= qr) return tr[u][c].sum;
// 递归查询子节点
int res = 0;
if(tr[ls(u)][c].sum) // 仅当左子节点存在时查询
res += query(c, ls(u), ql, qr);
if(tr[rs(u)][c].sum) // 仅当右子节点存在时查询
res += query(c, rs(u), ql, qr);
return res % mod;
}
signed main() {
cin >> n;
for(int i=1; i<=n; i++) {
int x, y, c;
cin >> x >> y >> c;
a[i] = {x, y, c};
}
sort(a+1, a+1+n);
for(int c=0; c<3; c++)
tr[1][c] = {1, N-10, 0};
int ans = 0, cur = 1; // ans统计答案,cur记录待更新位置
for(int i=1; i<=n; i++) {
int c = a[i].c, y = a[i].y;
// 查询互补颜色的线段树中宽度>y的数量
// oth[c][0]和oth[c][1]是当前颜色的两个互补颜色
ans = (ans + query(oth[c][0], 1, y+1, N-10)) % mod;
ans = (ans + query(oth[c][1], 1, y+1, N-10)) % mod;
// 延迟更新:当遇到更大的x时,将之前相同x的矩形插入线段树
if(i == n || a[i].x < a[i+1].x) {
for(; cur<=i; cur++) {
int cc = a[cur].c, yy = a[cur].y;
update(cc, 1, 1, N-10, yy); // 插入到对应颜色的线段树
}
}
}
cout << ans % mod << endl;
return 0;
}
男泵,树上差分叠加点