成都七中 解题报告
点分树有这么一个性质:你总能找到一个点,使得原树中这个点所在的连通块在这个点的子树内(如果整棵树没有被破坏,这个点就是根节点)。
这个结论过于显然,证明只有一句话:点分树上的联通就是原树上的联通,如果有在子树外的点,就一定是上方的点,所以还是有这么一个点存在。
那么显然,把询问放到原来的点上和放到这个点上,答案都是一样的,但放到这个点上就只需要处理子树上的问题了,所以我们把询问移动一下。
至于怎么找到这个点呢?在点分治时,我们可以统计当前根节点到可到达的点(点分树上的子树)的距离,给这些点开个 vector,把根和根到这个点路径上的最大最小值塞进去,之后如果这个点有询问,就把这些值取出来一一比较就行。
之前其实我也尝试过在点分树上暴力跳父亲的做法,但是点分树上的距离跟原树没有关系,也就是说还是要通过倍增计算两点路径上的最大最小值,实在算不动了,于是换了种方法。
这样搞完以后我们顺便记录一下点分树子树到根节点路径上的最大最小值,再加一个颜色,以后计算根节点的答案时就不用再遍历子树了。
现在对于每个节点,我们有她子树里每个节点的颜色与路径上大小值,我们还有“寄居”在这个节点上的问题,于是问题变成了:
给你 \(n\) 个结构体,每个结构体含有三个变量:\(l,r,col\)。再给你 \(m\) 个询问 \(ql,qr\),询问 \(l >= ql\) 且 \(r <= qr\) 的元素的 \(col\) 种类数。
据说这是个喜闻乐见的二维偏序,但我还是推了半个小时。。。下面是推出来的结果:
回忆 HH的项链(区间数颜色) 一题,我们可以设一个数组,表示每种颜色上一个元素在哪里,如果有更优的就可以替代掉。
接着,我们用将 \(l\) 从大到小排序解决 \(l>=ql\) 的限制,在新元素加入进来时与原来的元素进行比较(如果有的话),如果 \(r\) 比原元素小说明更优,就将其放到 \(r\) 的位置。然后随着 \(l\) 的变化,用树状数组统计和输进答案里就行。
可以看出这道题所有部分都可以在点分治上完成,但思想和点分树息息相关。
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int, int>
#define mp make_pair
#define For(i, a, b) for (int i = (a); i <= (b); ++i)
#define Rep(i, a, b) for (int i = (a); i >= (b); --i)
using namespace std;
void INIT()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
}
const int maxn = 100105;
int n, m;
int col[maxn];
int root;
vector<int> e[maxn], t[maxn];
bool vis[maxn];
int rt, mx[maxn], si[maxn], trTot, dep[maxn];
void findDeep(int u, int fa)
{
mn[u][0] = mxx[u][0] = u;
for (auto v : e[u])
{
if (v == fa)
continue;
pa[v][0] = u;
dep[v] = dep[u] + 1;
findDeep(v, u);
}
}
int Tot;
void findRt(int u, int fa)
{
mx[u] = 0;
si[u] = 1;
for (auto v : e[u])
{
if (vis[v] || v == fa)
continue;
findRt(v, u);
si[u] += si[v];
if (si[v] > mx[u])
mx[u] = si[v];
}
mx[u] = max(mx[u], Tot - si[u]);
if (mx[rt] > mx[u])
rt = u;
}
struct pp
{
int l, r, id;
};
vector<pp> w[maxn];
vector<pp> et[maxn];
void youcanttakemyAC(int u, int f, int mn, int mx)
{
w[rt].push_back(pp{mn, mx, col[u]});
et[u].push_back(pp{mn, mx, rt});
for(auto v : e[u])
{
if(vis[v] == 1 || f == v) continue;
youcanttakemyAC(v, u, min(mn, v), max(mx, v));
}
}
void you(int u)
{
rt = u;
for(auto v : e[u])
{
if (vis[v] == 1)
continue;
youcanttakemyAC(v, u, min(v, u), max(v, u));
}
}
int fa[maxn];
void build(int u)
{
w[u].push_back(pp{u, u, col[u]});
et[u].push_back(pp{u, u, u});
vis[u] = 1;
you(u);
for (auto v : e[u])
{
if (vis[v] == 1)
continue;
rt = 0;
Tot = si[v];
findRt(v, u);
fa[rt] = u;
t[u].push_back(rt);
t[rt].push_back(u);
build(rt);
}
}
void read()
{
cin >> n >> m;
int u, v;
for (int i = 1; i <= n; i++)
cin >> col[i];
For(i, 1, n - 1)
{
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
}
void Build()
{
rt = 0;
mx[0] = 2147483647;
findDeep(1, -1);
init();
Tot = n;
findRt(1, -1);
root = rt;
build(rt);
}
int ans[maxn];
void try_to_jump(int l, int r, int &x)
{
while (fa[x] && fa[x] >= l && fa[x] <= r)
x = fa[x];
}
struct tree
{
#define lowbit(x) (x & (-x))
int tr[maxn];
void add(int u, int k)
{
for (; u <= n; u += lowbit(u))
tr[u] += k;
}
void re(int u)
{
for (; u <= n; u += lowbit(u))
tr[u] = 0;
}
int que(int u)
{
int ans = 0;
for (; u >= 1; u -= lowbit(u))
ans += tr[u];
return ans;
}
} p;
bool cmp(pp a, pp b)
{
return a.l > b.l || (a.l == b.l && a.id > b.id);//先要算完贡献再询问
}
int tmd[maxn];
int main()
{
freopen("text.in", "r", stdin);
INIT();
read();
Build();
int l, r, x;
for (int i = 1; i <= m; i++)
{
cin >> l >> r >> x;
for(auto j : et[x])
{
// cout << x << ' ' << j.l << ' ' << j.r << ' ' << j.id << endl;
if(j.l >= l && j.r <= r)
{
// cout << j.id << endl;
w[j.id].push_back(pp{l, r, -i});
break;//先进来的深度小,最先进来的深度最小
}
}
}
for(int i = 1; i <= n; i++) {
sort(w[i].begin(), w[i].end(), cmp);
for(auto j : w[i])
{
if(j.id < 0)
ans[-j.id] = p.que(j.r);//用正负判断是询问还是子树元素,就可以一起排序了!
else
{
if(!tmd[j.id] || tmd[j.id] > j.r)
{
if(tmd[j.id]) p.add(tmd[j.id], -1);
tmd[j.id] = j.r;
p.add(tmd[j.id], 1);
}
}
}
for(auto j : w[i])
if(j.id > 0 && tmd[j.id])
{
p.add(tmd[j.id], -1);
tmd[j.id] = 0;
}
}
for (int i = 1; i <= m; i++)
cout << ans[i] << endl;
return 0;
}
如何判断两种方法哪个实现简单?看看哪个方法让你建立的额外的函数多就行了。
大家可不要和我一样犯调 2 个小时后重构的失误哦!