题意
给定一个长度为n的序列A[],你需要回答q个询问。每次给定两个端点,询问区间内不同颜色的个数。n, q < 1e5。
主要思路
可以考虑建立主席树,以位置做版本下标,以颜色离散化后的值做下标,记录该颜色前缀个数,然后版本信息相减即可。
给定一个长度为n的序列A[],你需要回答q个询问。每次给定两个端点,询问区间内不同颜色的个数。n, q < 1e5。
可以考虑建立主席树,以位置做版本下标,以颜色离散化后的值做下标,记录该颜色前缀个数,然后版本信息相减即可。
这道题的\(w\)很大,且要求出二维和。一种可行的办法就的是嵌套数据结构,第一想法是树状数组套线段树动态开点,时间复杂度为\(O(log^2 m)\)。但是空间依旧很大,可以发现线段树空间庞大,可以考虑替换为平衡树,空间极大减小,且时间复杂度不变。
进一步优化可以考虑对下标进行哈希。
显然需要树链剖分。
对于每一个 \(a \times dis + b\) 的操作,向懒惰标记里添加。如果碰上已经有懒惰信息,那么我们只需要把这样的操作理解为加上直线,把直线下边缘下传到左右子节点即可。(计算长度进行左右决策下传)对于每一个节点,懒惰加法为等差数列求和,计算 \(b \times subtreeSize + a \times \frac{subtreeSize*(subtreeSize-1)}{2}\)。
可并堆思路:自顶向上做大根堆,然后弹出堆顶直到整个堆的和小于 m。(性质:父亲节点不会选择子树不包含的忍者)。
线段树合并思路:没看懂,不写了。
// P1552.cpp #include <iostream> #include <cstdio> #include <algorithm> #include <vector> #define ll long long using namespace std; const int MAX_N = 100100; struct node { int lson, rson; ll dist; } nodes[MAX_N]; ll n, m, li[MAX_N], ci[MAX_N], master, ans = -2e9; ll sum[MAX_N], siz[MAX_N], pt[MAX_N]; vector<int> G[MAX_N]; int merge(int x, int y) { if (!x || !y) return x + y; if (ci[x] < ci[y]) swap(x, y); nodes[x].rson = merge(nodes[x].rson, y); if (nodes[nodes[x].lson].dist < nodes[nodes[x].rson].dist) swap(nodes[x].lson, nodes[x].rson); nodes[x].dist = nodes[nodes[x].rson].dist + 1; return x; } void dfs(int u) { sum[u] = ci[u], siz[u] = 1, pt[u] = u; int siz_ = G[u].size(); for (int i = 0; i < siz_; i++) { int to = G[u][i]; dfs(to); sum[u] += sum[to], siz[u] += siz[to]; pt[u] = merge(pt[u], pt[to]); } while (sum[u] > m && siz[u]) { sum[u] -= ci[pt[u]]; pt[u] = merge(nodes[pt[u]].rson, nodes[pt[u]].lson); siz[u]--; } ans = max(ans, li[u] * siz[u]); } int main() { scanf("%lld%lld", &n, &m); for (int i = 1; i <= n; i++) { ll fa; scanf("%lld%lld%lld", &fa, &ci[i], &li[i]); if (fa == 0) master = i; else G[fa].push_back(i); } dfs(master); printf("%lld", ans); return 0; }
我之前把数据结构搞复杂了,看了题解发现好简洁。还是要做减法啊。