首页 > 其他分享 >【题解】P3920 [WC2014]紫荆花之恋

【题解】P3920 [WC2014]紫荆花之恋

时间:2023-04-29 16:13:53浏览次数:38  
标签:rt insert 紫荆花 int 题解 fa P3920 dep operatorname

思路

点分树 + 根号重构 + *高速平衡树。

点分树的两种常见用法无非是 直接做和路径有关的暴力 还有 处理这种有关单点和整树的问题,后者的另一个经典题目是 P3241 [HNOI2015]开店。

回到这个题目,处理路径考虑先上点分治,暂时不考虑强制在线的限制。

因为每次加上一个新点,所以可以考虑在加点的过程中动态维护答案,于是问题变成维护每次加点之后答案的增量。

也就是说对于新加入的点 \(u\),每次询问原树中满足 \(\operatorname{dist}(u, v) \leq r_u + r_v\) 的点 \(v\) 的个数。

等式变形成 \(\operatorname{dist}(u, v) - r_u \leq r_v\),似乎不太好在点分的过程中处理 \(\operatorname{dist}(u, v)\).

一般情况下考虑的是把 \(u, v\) 之间的路径拆成 \(\{u, \cdots, \operatorname{lca}(u, v)\}\) 和 \(\{\operatorname{lca}(u, v), \cdots, v\}\) 两部分,但是 \(\operatorname{lca}\) 在点分的过程中难以钦定。

又因为实际上可以从路径上的任意一点断开,所以可以联想到点分树的一个很好的性质:点分树上两点的 \(\operatorname{lca}\) 在原树中一定在这两点的路径上,并且点分树的树高是 \(O(\log)\) 的。

这意味着我们可以从点 \(u\) 向上枚举 \(\operatorname{lca}\) 然后处理点 \(v\) 的个数。

先考虑静态询问怎么做。假设当前枚举到 \(u\) 的祖先 \(x\)。原式可以化成 \(\operatorname{dist}(u, x) + \operatorname{dist}(x, v) \leq r_u + r_v\),调整成关于 \(v\) 的限制就是 \(\operatorname{dist}(u, x) - r_u \leq r_v - \operatorname{dist}(x, v)\).

注意到等式的左边在枚举祖先的时候可以视作一个定值,于是我们只需要知道 \(x\) 的子树中满足 \(r_v - \operatorname{dist}(x, v)\) 大于等于某一个定值的点的个数就可以了。

常规的树论做法都不太可行,考虑一些和点分树性质有关的暴力。

因为点分树的树高是 \(O(\log)\) 级别的,所以对于每个结点大力存储下它子树中信息的总复杂度是 \(O(n \log n)\).

这意味着我们可以预先将 \(x\) 子树中所有的结点的 \(r_v - \operatorname{dist}(x, v)\) 用某种神秘的数据结构维护一下,之后询问就是在上面查询的事情。

因为动态加点没法离散化,所以选择用平衡树实现。

注意到向上枚举的过程中会算重子结点的子树,所以还需要维护 \(r_v - \operatorname{dist}(fa_x, v)\) 用来容斥。

然后再考虑如何维护动态插入结点。

不考虑点分树的性质,那么直接将新点接在原树父亲下面就行,可以看作是钦定在父亲处点分一次,然后在这个孤点在进行一次点分。现在的问题是这样做不能保证复杂度。

于是考虑类似替罪羊树的思路,当某棵子树失衡的时候就暴力调整。钦定一个比例系数 \(\alpha\),如果插入点 \(u\) 后其祖先 \(x\) 满足 \(\operatorname{size}(x) \geq \alpha \cdot \operatorname{size}(fa_x)\),就直接暴力重建 \(x\) 的子树。

具体的复杂度就是调参,\(\alpha\) 在 \(0.8\) 左右的时候点分树的树高大概还是 \(O(\log)\)(但我不会证,有懂的老哥麻烦教一下

如果要再偷懒的话可以不写平衡树,用 vector + 根号重构平替。具体考虑维护两个 vector,一个当作缓存池存当前加入的数据,另一个用来存溢出的数据。当缓存池超过根号大小的时候就暴力归并。

查询直接在 vector 里二分。

这个在很多阴间根号题都有应用,\(O(n \sqrt{n})\) 应该比较快。

复杂度懒得算了,反正常数已经挺离谱了。

代码

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

#define il inline
typedef long long ll;

const int maxn = 1e5 + 5;
const int maxe = maxn << 1;
const int lg_sz = 17;
const int lim = 350;
const int inf = 0x3f3f3f3f;
const double alpha = 0.95;

struct node
{
	int to, nxt;
} edge[maxe];

int n, ecnt;
int r[maxn], head[maxn], sz[maxn];
bool vis[maxn];
ll last_ans;

namespace IO
{
    //by cyffff
	int len = 0;
	char ibuf[(1 << 20) + 1], *iS, *iT, out[(1 << 26) + 1];
	#define gh() (iS == iT ? iT = (iS = ibuf) + fread(ibuf, 1, (1 << 20) + 1, stdin), (iS == iT ? EOF : *iS++) : *iS++)
	#define reg register

	il int read()
    {
		reg char ch = gh();
		reg int x = 0;
		reg char t = 0;
		while (ch < '0' || ch > '9') t |= ch == '-', ch = gh();
		while (ch >= '0' && ch <= '9') x = x * 10 + (ch ^ 48), ch = gh();
		return t ? -x : x;
	}

	il void putc(char ch) { out[len++] = ch; }

	template<class T>

	il void write(T x)
    {
		if (x < 0) putc('-'), x = -x;
		if (x > 9) write(x / 10);
		out[len++] = x % 10 + 48;
	}

	il void flush()
    {
		fwrite(out, 1, len, stdout);
		len = 0;
	}
}
using IO::read;
using IO::write;
using IO::flush;
using IO::putc;

il void add_edge(int u, int v)
{
	ecnt++;
	edge[ecnt].to = v, edge[ecnt].nxt = head[u];
	head[u] = ecnt;
}

il pair<int, int> get_rt(int u, int f, int tot)
{
	sz[u] = 1;
	int res = 0;
	pair<int, int> ans = make_pair(inf, 0);
	for (int i = head[u], v; i; i = edge[i].nxt)
	{
		v = edge[i].to;
		if ((!vis[v]) && (v != f))
		{
			ans = min(ans, get_rt(v, u, tot));
			sz[u] += sz[v], res = max(res, sz[v]);
		}
	}
	ans = min(ans, make_pair(max(res, tot - sz[u]), u));
	return ans;
}

namespace tree
{
	int f[lg_sz][maxn], dep[maxn], dis[maxn];

	il void insert(int u, int fa, int w)
	{
		f[0][u] = fa, dep[u] = dep[fa] + 1, dis[u] = dis[fa] + w;
		for (int i = 1; i < lg_sz; i++) f[i][u] = f[i - 1][f[i - 1][u]];
	}

	il int lca(int u, int v)
	{
		if (dep[u] < dep[v]) swap(u, v);
		for (int i = lg_sz - 1; ~i; i--)
			if (dep[f[i][u]] >= dep[v]) u = f[i][u];
		if (u == v) return u;
		for (int i = lg_sz - 1; ~i; i--)
			if (f[i][u] != f[i][v]) u = f[i][u], v = f[i][v];
		return f[0][u];
	}

	il int calc_dis(int u, int v) { return dis[u] + dis[v] - 2 * dis[lca(u, v)]; }
}

struct item
{
	vector<int> lrg, sml;

	il void insert(int w)
	{
		sml.insert(lower_bound(sml.begin(), sml.end(), w), w);
		if (sml.size() >= lim)
		{
			vector<int> res, emp;
			int i = 0, j = 0;
			for ( ; (i < lrg.size()) && (j < sml.size()); )
				if (lrg[i] < sml[j]) res.push_back(lrg[i++]);
				else res.push_back(sml[j++]);
			while (i < lrg.size()) res.push_back(lrg[i++]);
			while (j < sml.size()) res.push_back(sml[j++]);
			swap(lrg, res), swap(sml, emp);
		}
	}

	il int calc_rk(int w) { return (upper_bound(lrg.begin(), lrg.end(), w) - lrg.begin()) + (upper_bound(sml.begin(), sml.end(), w) - sml.begin()) - 2; }

	il void clear() { lrg.clear(), sml.clear(); }
} subt[maxn << 1], tof[maxn << 1];

namespace divide
{
	int fa[maxn], dep[maxn], siz[maxn];

	il int query(int u, int w)
	{
		int res = 0;
		for (int i = u, j; fa[i]; i = j)
		{
			j = fa[i];
			int x = w - tree::calc_dis(u, j);
			res += subt[j].calc_rk(x) - tof[i].calc_rk(x);
			// printf("debug %d -> %d, %d\n", i, u, fa[i]);
		}
		return res;
	}

	il void insert(int u, int w, int anc)
	{
		subt[u].insert(-w);
		// puts("subt insert done");
		for (int i = u, j; i != anc; i = j)
		{
			j = fa[i];
			// printf("for %d %d\n", i, fa[i]);
			int x = tree::calc_dis(u, j) - w;
			if (j != anc) subt[j].insert(x);
			tof[i].insert(x);
		}
		// puts("insert ok");
	}

	il void clear(int u, int fa, int anc_dep)
	{
		vis[u] = false;
		for (int i = head[u], v; i; i = edge[i].nxt)
		{
			v = edge[i].to;
			if ((v != fa) && (dep[v] > anc_dep)) clear(v, u, anc_dep);
		}
	}

	il int build(int u, int tot_sz, int f, int anc)
	{
		// puts("begin build");
		int rt = get_rt(u, 0, tot_sz).second, bas = sz[u];
		subt[rt].clear(), tof[rt].clear();
		// if (fa[rt] == f) printf("cir %d\n", rt);
		fa[rt] = f, siz[rt] = 1, vis[rt] = true, dep[rt] = dep[f] + 1;
		// puts("begin insert");
		// if (fa[rt] == f) printf("debg %d\n", u);
		insert(rt, r[rt], anc);
		// puts("insert done");
		for (int i = head[rt], v; i; i = edge[i].nxt)
		{
			v = edge[i].to;
			if (!vis[v]) siz[rt] += build(v, bas, rt, anc);
		}
		// puts("build done");
		return siz[rt];
	}

	il void rebuild(int u) { clear(u, 0, dep[u]), build(u, siz[u], fa[u], fa[u]); }

	il void insert(int u, int w)
	{
		subt[u].insert(-w), siz[u]++;
		int nd = 0;
		for (int i = u, j; fa[i]; i = j)
		{
			j = fa[i];
			// printf("cur %d : %d\n", i, fa[i]);
			// if ((i == 86) && (fa[i] == 85)) printf("debug %d %d\n", u, w);
			int x = tree::calc_dis(u, j) - w;
			siz[j]++, subt[j].insert(x), tof[i].insert(x);
			if (siz[i] > siz[j] * alpha) nd = j;
			// if (last_ans == 5433) printf("doadmoadnd %d\n", j);
			// if (last_ans == 5433 && j == 85) printf("%d %d %d %d\n", i, j, siz[i], siz[j]);
		}
		// if (last_ans == 5433) printf("debug nd = %d, %d\n", nd, siz[85]);
		if (nd) rebuild(nd);
		// puts("solve done");
	}
}

int main()
{
	// freopen("P3920_3.in", "r", stdin);
	// freopen("P3920_3.res", "w", stdout);
	read(), n = read();
	for (int i = 1; i <= n; i++)
	{
		vis[i] = true;
		int fa = read() ^ last_ans % (int)1e9, c = read();
		// printf("read fa = %d\n", fa);
		r[i] = read();
		tree::insert(i, fa, c);
		if (i > 1) add_edge(fa, i), add_edge(i, fa);
		divide::fa[i] = fa, divide::dep[i] = divide::dep[fa] + 1;
		write(last_ans += divide::query(i, r[i])), putc('\n');
		divide::insert(i, r[i]);
		// printf("done %lld\n", last_ans);
	}
	flush();
	return 0;
}

标签:rt,insert,紫荆花,int,题解,fa,P3920,dep,operatorname
From: https://www.cnblogs.com/lingspace/p/p3920.html

相关文章

  • CF1656F Parametric MST 题解
    为了便于解题,先对\(a\)数组从小到大进行排序。首先,根据定义可以得出总价值的表达式:\[\begin{aligned}W&=\sum\limits_{(u,v)\inE}[a_ua_v+t(a_u+a_v)]\\&=\sum\limits_{(u,v)\inE}a_ua_v+t\sum\limits_{(u,v)\inE}(a_u+a_v)\end{aligned}\]接着,我们需要发现一个比较......
  • P6818 [PA2013]Działka 题解
    P6818[PA2013]Działka前言我太菜了。。。。对着jiangly大佬的题解研究了一下午研究了一下午才搞出来(泪目。作为一个蒟蒻,我就详细的讲一下我对与本题的理解。题意本题的的题意描述的还是比较明了。在二维坐标系中,输入\(n\)个点\(m\)次询问,每次询问,给出一个矩阵,......
  • 天池编程大赛周赛-Character deletion 题解
    题目描述EntertwostringsanddeleteallcharactersinthesecondstringfromthefirststringStringcontainsspaces$1\leqlen(str),len(sub)\leq10^5$示例示例1:Input:str=”Theyarestudents”,sub=”aeiou”Output:”Thyrstdnts”来源:九章算法链接:ht......
  • P3573 [POI2014]RAJ-Rally 题解
    非常好题目,爱来自xc。看到有向无环图,想到拓扑序。通过拓扑序,可以轻松求出以每个点为起点的最长路\(disS\)与每个点为终点的最长路\(disF\)。如何求总共的最长路?在\(disS,disF,disS_u+1+disF_v((u,v)\inE)\)中取最大值即可。注意最后一项,表示将连边的二点值相加。......
  • 题解 CF1264D1
    前言数学符号约定:\(\dbinom{n}{m}\):表示\(n\)选\(m\)。如非特殊说明,将会按照上述约定书写符号。题目分析:考虑题目的问题弱一点的版本,假设此时我们的括号序列是确定的如何求其括号匹配的最深深度。如果你有些许dp基础的话,不难想到如下做法:考虑位置\(i\),将区间\([1,......
  • 题解 CF1264D2
    前言建议大家看一下我对于D1的题解(传送门)后再看本题解,本题解是基于那篇题解的基础上书写的。数学符号约定\(\dbinom{n}{m}\):表示\(n\)选\(m\)。如非特殊说明,将会按照上述约定书写符号。题目分析首先引用一下D1的答案:\(\displaystyle\sum_{i=1}^n\displaystyle\sum_{......
  • [ARC125E] Snack 题解
    不难发现一个较简单的网络流模型:源点向所有糖果\(i\)连\(a_i\)的容量;所有糖果向所有人\(i\)连\(b_i\)的容量;所有人\(i\)向汇点连\(c_i\)的容量。但第二步中建出的边数达到了惊人的\(O(nm)\),显然过不去。考虑优化。从最大流角度优化较困难,由于最大流等价于最小......
  • P4423 题解
    前言题目传送门!更好的阅读体验?刚学分治就来写篇题解纪念一下,其实和平面最近点对一样的(总共四倍经验!)。思路根据P7883的分治思路,这题我们可以考虑用相似的方法解决。首先将点集按\(x\)坐标从小到大排序。然后分治。对于\(\left[l,r\right]\)区间,分治为\(\left[l,mid......
  • 题解 P3225 [HNOI2012] 矿场搭建
    解析传送门一道简单的tarjan题题意:在无向图中找一些点,这些点组成的的点集记为\(V\),使得去掉任意一个点,剩下的每一个点都可以到达\(V\)中任意一个点,求点集\(V\)的大小的最小值及其方案数。去掉一个点,很自然的联想到割点,那么考虑一下割点在不在备选集合中。如图,显然可以看出,......
  • Codeforces Round 868 (Div. 2) A-E题解
    比赛地址这把真不在状态,麻了,看来还得再练A.A-characteristic题意:给出n和k,要求构造只含1和-1数组a,存在k对(i,j)(i≠j),有a[i]*a[j]=1Solution令构造的数组有x个1和y个-1,那么其对于答案的贡献有\[x*(x-1)/2+y*(y-1)/2\]又因为有x+y=n,所以可以转化成关于x的一元二次方程化简后......