P5064 [Ynoi2014] 等这场战争结束之后 题解

毒瘤卡常分块题但是看你写法。

注意到这道题有回溯操作,可以考虑建立操作树,下设第 \(i\) 个操作在操作树上对应的节点为 \(p_i\)

先建立 0 号节点表示初始状态(令 \(p_0=0\)),对于 1,3 两个非回溯操作(设为第 \(i\) 个操作),当前操作编号就是操作树上对应的节点编号,即令 \(p_i=i\);对于 2 这个回溯操作,由于我们知道其会回溯到哪一步(设为第 \(j\) 步),则令 \(p_i = p_j\)

最后对 1,3 操作,连边 \(p_{i-1},p_i\),边上的权值就是当前操作。

这样做,我们就成功的解决了这个烦人的回溯操作,因为我们只需要遍历这棵操作树就可以得到答案,而遍历操作树时我们只需要考虑一条边的插入与删除,和一个询问,和线段树分治有点像。

然后先对权值离散化,使用可撤销并查集维护当前图的连通性,对每一个点我们进行值域分块,每一个点在并查集上的根节点维护当前所在连通块的权值,查询的时候在对应的根节点上查询第 k 小。

接下来讲讲我的写法:

  1. 由于我的写法特别卡(#16 499ms AC),所以我的代码不见得有参考价值(但是对的),请谨慎参考。
  2. 实际操作中建议采用启发式合并维护并查集,虽然这比按轶合并慢但是操作 3 出现 k > 当前子树大小时可以直接判掉。
  3. 建议多封装几个函数(但是不要太多,封装比较复杂的操作),比如说 dfs 中对于一条边的合并、撤销以及查询操作,可以分别写 3 个函数维护,实测比全堆在 dfs 函数中快。
  4. unsigned int 比 int 快。

CodeBase:CodeBase of Plozia

Code:

/*
========= Plozia =========
	Author:Plozia
	Problem:P5064 [Ynoi2014] 等这场战争结束之后
	Date:2022/7/9
========= Plozia =========
*/

#include <bits/stdc++.h>
typedef long long LL;

const int MAXN = 1e5 + 1;
unsigned int n, m, Head[MAXN], cntEdge, Block = 4250;
int ans[MAXN];
unsigned int fa[MAXN], Size[MAXN];
unsigned short f[MAXN][25];
struct node { unsigned int opt, x, y; } q[MAXN];
struct EDGE { unsigned int To, Next; } Edge[MAXN];
struct NODE
{
	unsigned int a, id;
	bool operator <(const NODE &fir)const { return a < fir.a; }
}a[MAXN];

int Read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum * 10) + (ch ^ 48);
	return sum * fh;
}
void addEdge(int From, int To)
{
	++cntEdge; Edge[cntEdge] = (EDGE){To, Head[From]}; Head[From] = cntEdge;
}
int gf(int x)
{
	while (fa[x] != x) x = fa[x];
	return x;
}

void Merge(int &fx, int &fy)
{
	if (Size[fx] > Size[fy]) fx ^= fy ^= fx ^= fy;
	fa[fx] = fy; Size[fy] += Size[fx];
	for (int j = 1; j <= 24; ++j) f[fy][j] += f[fx][j];
}
void Split(int fx, int fy)
{
	fa[fx] = fx; Size[fy] -= Size[fx];
	for (int j = 1; j <= 24; ++j) f[fy][j] -= f[fx][j];
}

void Calc(int fx, int u)
{
	int sum = 0, flag = 0;
	for (int j = 1; j <= 24; ++j)
	{
		if (sum + f[fx][j] < q[u].y) sum += f[fx][j];
		else { flag = j; break ; }
	}
	for (int j = (flag - 1) * Block + 1, pz = std::min(flag * Block, n); j <= pz; ++j)
	{
		if (gf(a[j].id) == fx) ++sum;
		if (sum == q[u].y) { ans[u] = a[j].a; break ; }
	}
}

void dfs(int now)
{
	for (int i = Head[now]; i; i = Edge[i].Next)
	{
		int u = Edge[i].To;
		if (q[u].opt == 1)
		{
			int fx = gf(q[u].x), fy = gf(q[u].y);
			if (fx != fy) Merge(fx, fy);
			dfs(u);
			if (fx != fy) Split(fx, fy);
		}
		else if (q[u].opt == 3)
		{
			int fx = gf(q[u].x);
			if (q[u].y > Size[fx]) { ans[u] = -1; dfs(u); continue ; }
			Calc(fx, u); dfs(u);
		}
		else dfs(u);
	}
}

int main()
{
	n = Read(), m = Read(); for (int i = 1; i <= n; ++i) a[i].a = Read(), a[i].id = i;
	std::sort(a + 1, a + n + 1);
	for (int i = 1; i <= m; ++i) { q[i].opt = Read(), q[i].x = Read(); if (q[i].opt != 2) q[i].y = Read(); }
	for (int i = 1; i <= m; ++i)
	{
		if (q[i].opt != 2) addEdge(i - 1, i);
		else addEdge(q[i].x, i);
	}
	for (int i = 1; i <= n; ++i) fa[i] = i, Size[i] = 1, ++f[a[i].id][(i - 1) / Block + 1];
	dfs(0);
	for (int i = 1; i <= m; ++i)
		if (q[i].opt == 3) printf("%d\n", ans[i]);
	return 0;
}
posted @ 2022-07-10 17:56  Plozia  阅读(50)  评论(0编辑  收藏  举报