一道题
给定一棵 \(n\) 个节点的树,每个节点 \(u\) 上有一个可容纳 \(k_u\) 个球的桶,\(m\) 次操作:给一个节点到根路径上的所有节点的桶内放一个有颜色的球,如果节点的桶满了则不能放进这个节点,\(q\) 次询问某节点的桶内的球有多少种颜色。
\(n, m, q \le 10^5\)
因为节点上的桶有容量,所以只有某时刻前的操作会对该节点产生影响。
记 \(Max_u\) 表示\(u\)号节点上的桶恰好装满的时刻,考虑求出每个节点的 \(Max\),将所有询问以 \(Max\) 为关键字升序排序,问题就转化为单点加球,子树查询颜色数的问题。
求 \(Max\):
\(Max_u\) 按照定义是所有覆盖 \(u\) 节点的操作的时间第 \(k_u\) 小,而所有覆盖 \(u\) 节点的操作是下端点在 \(u\) 子树内的操作,如果能将这些操作放到同一区间上,就可以用主席数实现区间第 \(k\) 小。
如果对每个点都至多有一次操作以它为下端点,将这个节点的 dfs 序作为对应操作的下标,操作的时间为值,问题就转化成了区间第 \(k\) 小。
但对每个点都可能作为多次操作的下端点,需要将每个结点扩充成一个区间,区间长度为这个节点对应操作的数量,然后就可以类别上一段所说的方法求出 \(Max\)。
1
2
3
4
5
6
7
8
9rep(i, 1, m) {
scanf("%d%d", &x[i], &c[i]);
tim[dfn[x[i]]].push_back(i);
}
rep(i, 1, n) {
R[i] = R[i-1]; //R[i] 是 dfs 序为 i 的节点对应区间的右端点
for(int t : tim[i]) SegTree::upd(t, R[i]), R[i]++;
}
rep(i, 1, n) Max[i] = SegTree::qry(k[i], R[dfn[i]-1], R[suf[i]]);单点加球,子树查询颜色数的问题:
考虑静态查询子树查询颜色数的问题,做法是每个有颜色的结点处 \(+1\),每个结点和同种颜色,
dfs
序小于这个结点且dfs
序最大的结点的lca
处 \(-1\)。如图
在 \(4,5\) 号结点的
lca
,\(1\) 号结点处 \(-1\)。在 \(5,6\) 号结点的
lca
,\(1\) 号结点处 \(-1\)。...
然后查询子树和就是子树颜色数。
再考虑单点加球,子树查询颜色数的问题。其实只需要加一个
set
数组和树状数组,set
数组维护当前每种颜色结点的dfs
序,单点加球时在set
中找出前驱和后继,这个点处 \(+1\),它和前驱的lca
处 \(-1\),和后继的lca
处 \(-1\),前驱和后继的lca
处 \(+1\),然后查询子树和。1
2
3
4
5
6
7
8
9
10int ci = c[i], dfnx = dfn[x[i]];
if(s[ci].count(dfnx)) return;
s[ci].insert(dfnx), add(dfnx, 1);
auto it = s[ci].find(dfnx);
int pre = 0, suf = 0;
if(it != s[ci].begin()) pre = *(--it), it++;
if(it != --s[ci].end()) suf = *(++it), it--;
if(pre) upd(pre, dfnx, -1);
if(suf) upd(suf, dfnx, -1);
if(pre && suf) upd(pre, suf, 1);
时间复杂度 \(O(n\log n)\)。
完整代码:
1 |
|