【BZOJ 4539】【HNOI 2016】树

http://www.lydsy.com/JudgeOnline/problem.php?id=4539

今天测试唯一会做的一道题。

按题目要求,如果暴力的把模板树往大树上仍,最后得到的大树是$O(n^2)$级别的,不能存储,更不能做了。

把模板树往大树上扔的过程我想象成了两个大节点进行连边,每个大节点代表模板树本身或一部分。

这相当于把初始的大树(此时和模板树相同)缩成一个大节点,每次把模板树的一部分缩成一个大节点往大节点构成的大树上连,最后连好的大节点构成的模板树是$O(n)$级别的。

每个节点里都套着一棵树,像树套树的模型。

这样在求距离和LCA的时候就可以先找到节点在哪个大节点里,求出在大节点内的一部分距离后,再在大节点构成的大树上倍增到LCA,统计距离。在LCA(大节点)中套着的树中继续倍增求LCA,统计距离。

每个大节点要维护的信息比较多;因为要按编号顺序从小到大加,所以还要在模板树的dfs序上用主席树查询第k大;倍增时还要注意特判几种特殊情况。在这里不再一一赘述。

测试时只有60分,后来拿到数据亲测一遍后发现到后期大树的节点到达$O(n^2)$级别,此时节点编号用int已经存不下了,所以存节点编号需要用long long。看来我还是too naive!!!

时间复杂度$O(nlogn)$

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 100003;
int in() {
	int k = 0, fh = 1; char c = getchar();
	for(; c < ‘0‘ || c > ‘9‘; c = getchar())
		if (c == ‘-‘) fh = -1;
	for(; c >= ‘0‘ && c <= ‘9‘; c = getchar())
		k = (k << 3) + (k << 1) + c - ‘0‘;
	return k * fh;
}

ll inll() {
	ll k = 0; int fh = 1; char c = getchar();
	for(; c < ‘0‘ || c > ‘9‘; c = getchar())
		if (c == ‘-‘) fh = -1;
	for(; c >= ‘0‘ && c <= ‘9‘; c = getchar())
		k = (k << 3) + (k << 1) + c - ‘0‘;
	return k * fh;
}

int n, m, q;

namespace Small {
	struct node {
		int nxt, to;
	} E[N << 1];
	int cnt = 0, point[N], f[N][18], deep[N], L[N], R[N], a[N];
	bool vis[N];
	void ins(int u, int v) {
		E[++cnt].nxt = point[u]; E[cnt].to = v; point[u] = cnt;
	}

	void dfs(int x) {
		vis[x] = true; L[x] = ++cnt; a[cnt] = x;
		for(int i = point[x]; i; i = E[i].nxt) if (!vis[E[i].to]) {
			deep[E[i].to] = deep[x] + 1;
			f[E[i].to][0] = x;
			dfs(E[i].to);
		}
		R[x] = cnt;
	}

	struct node2 {
		int l, r, s;
	} T[N * 20];
	int root[N], tot = 0;

	void update(int &pos, int l, int r, int key) {
		T[++tot] = T[pos]; pos = tot; ++T[pos].s;
		if (l == r) return;
		int mid = (l + r) >> 1;
		if (key <= mid) update(T[pos].l, l, mid, key);
		else update(T[pos].r, mid + 1, r, key);
	}
	void BuildPT() {
		for(int i = 1; i <= n; ++i) {
			root[i] = root[i - 1];
			update(root[i], 1, n, a[i]);
		}
	}

	void work() {
		int u, v;
		for(int i = 1; i < n; ++i) {
			u = in(); v = in();
			ins(u, v); ins(v, u);
		}
		cnt = 0; memset(vis, 0, sizeof(vis));
		deep[1] = 0; dfs(1);
		for(int j = 1; j < 18; ++j)
			for(int i = 1; i <= n; ++i) {
				f[i][j] = f[f[i][j - 1]][j - 1];
			}
		BuildPT();
	}

	int Sum(int x) {return R[x] - L[x] + 1;}

	int kth(int left, int right, int l, int r, int key) {
		if (l == r) return l;
		int mid = (l + r) >> 1, sum = T[T[right].l].s - T[T[left].l].s;
		if (sum >= key) return kth(T[left].l, T[right].l, l, mid, key);
		else return kth(T[left].r, T[right].r, mid + 1, r, key - sum);
	}
	int Query(int rt, int k) {
		int l = L[rt], r = R[rt];
		return kth(root[l - 1], root[r], 1, n, k);
	}

	int todeep(int a, int b) {return deep[a] - deep[b];}

	int LCA(int u, int v) {
		if (deep[u] < deep[v]) swap(u, v);
		int d = deep[u] - deep[v];
		for(int i = 17; i >= 0; --i)
			if ((1 << i) & d)
				u = f[u][i];
		if (u == v) return d;
		for(int i = 17; i >= 0; --i)
			if (f[u][i] != f[v][i]) {
				u = f[u][i]; v = f[v][i];
				d += (1 << (i + 1));
			}
		return d + 2;
	}
}

namespace Big {
	ll c[N][18];
	int f[N][18], deep[N];
	int tablenum = 0, table_to[N], table_rt[N];
	ll table_l[N], table_r[N], up;

	int pos(ll x) {
		int mid, left = 1, right = tablenum;
		while (left < right) {
			mid = (left + right) >> 1;
			if (x < table_l[mid]) right = mid - 1;
			else if (x > table_r[mid]) left = mid + 1;
			else return mid;
		}
		return left;
	}

	void work() {
		int a; ll b;
		up = n;
		tablenum = 1; table_l[1] = 1; table_r[1] = n; table_rt[1] = 1;
		for(int i = 1; i <= m; ++i) {
			a = in(); b = inll();
			int P = pos(b);
			table_rt[++tablenum] = a;
			table_l[tablenum] = up + 1;
			up += Small::Sum(a);
			table_r[tablenum] = up;
			f[tablenum][0] = P;
			deep[tablenum] = deep[P] + 1;
			table_to[tablenum] = Small::Query(table_rt[P], b - table_l[P] + 1);
			c[tablenum][0] = Small::todeep(table_to[tablenum], table_rt[P]) + 1;
		}
		for(int j = 1; j < 18; ++j)
			for(int i = 1; i <= tablenum; ++i) {
				f[i][j] = f[f[i][j - 1]][j - 1];
				c[i][j] = c[i][j - 1] + c[f[i][j - 1]][j - 1];
			}

		int changeu, changev, posu, vnum, posv, u, v;
		ll U, V, ret;
		for(int i = 1; i <= q; ++i) {
			U = inll(); V = inll(); ret = 0;
			posu = pos(U); posv = pos(V);
			if (deep[posu] < deep[posv]) {
				swap(posu, posv); swap(U, V);
			}

			changeu = Small::Query(table_rt[posu], U - table_l[posu] + 1);
			changev = Small::Query(table_rt[posv], V - table_l[posv] + 1);

			if (posu == posv) {
				printf("%d\n", Small::LCA(changeu, changev));
				continue;
			}

			u = posu; v = posv;
			for(int i = 17; i >= 0; --i)
				if (deep[f[u][i]] > deep[v]) {
					ret += c[u][i];
					u = f[u][i];
				}

			if (f[u][0] == v) {
				ret += 1;
				u = table_to[u];
				v = changev;
				ret += Small::LCA(u, v);
				ret += Small::todeep(changeu, table_rt[posu]);
			} else {
				if (deep[u] > deep[v]) {
					ret += c[u][0];
					u = f[u][0];
				}
				for(int i = 17; i >= 0; --i)
					if (f[u][i] != f[v][i]) {
						ret += (c[u][i] + c[v][i]);
						u = f[u][i];
						v = f[v][i];
					}
				ret += 2;
				ret += Small::LCA(table_to[u], table_to[v]);
				ret += Small::todeep(changeu, table_rt[posu]) + Small::todeep(changev, table_rt[posv]);
			}

			printf("%lld\n", ret);
		}
	}
}

int main() {
	n = in(); m = in(); q = in();

	Small::work();
	Big::work();
	return 0;
}

遇到看到不可做的题一定要认真地思考,看透每个操作的本质,找到操作中重复的东西并利用它化简空间复杂度和时间复杂度。一定要想好了在写,考虑到方方面面,任何细节都很关键。就像这次测试因为节点编号没有用long long存储导致$O(nlogn)$复杂度的得分被卡成$O(n^2)$复杂度的得分。

时间: 2024-08-07 00:17:50

【BZOJ 4539】【HNOI 2016】树的相关文章

BZOJ 2243: [SDOI2011]染色 树链剖分

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1886  Solved: 752[Submit][Status] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依次完成这m个操作. In

【BZOJ】3319: 黑白树

http://www.lydsy.com/JudgeOnline/problem.php?id=3319 题意:给一棵n节点的树(n<=1e6),m个操作(m<=1e6),每次操作有两种:1.查询u到根的第一条黑边的编号.2.将u到v的路径全部染成黑色 #include <cstdio> #include <cstring> #include <cmath> #include <string> #include <iostream>

BZOJ 1912 巡逻(树直径)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1912 题意:给出一棵树,边权为1.现在加一条或两条边后,使得从1出发遍历每个点至少一次再回到1的路程最短. 思路:先求一次树的直径Max1.然后将直径的边权改为-1,再求一次直径Max2.答案为ans=(n-1)*2-(Max1-1)-(Max2-1). struct node { int u,v,w,next; }; node edges[N<<1]; int head[N],e;

Bzoj 4408: [Fjoi 2016]神秘数 可持久化线段树,神题

4408: [Fjoi 2016]神秘数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 177  Solved: 128[Submit][Status][Discuss] Description 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1,4,13}, 1 = 1 2 = 1+1 3 = 1+1+1 4 = 4 5 = 4+1 6 = 4+1+1 7 = 4+1+1+1 8无法表示为集合S的子集的

BZOJ 1212 HNOI 2004 L语言 Trie树

题目大意:给出一些单词,和一些句子,当且仅当句子可以分割成的子串都可以被词典翻译,就说明这个子串是可以被翻译的.求最长的可以被翻译的前缀长度. 思路:利用Trie树来刷数组,能够刷到的最长的地方就是这个串最长可以翻译到的地方. PS:在BZOJ上Trie居然比AC自动机快,我的渣代码都刷到第一篇了... CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorith

[BZOJ 720][JZYZOJ 2016]gty的妹子树 强制在线 树分块/树套树

jzyzoj的p2016 先码着,强制在线的树分块或者树套树?关键是我树分块还在入门阶段树套树完全不会啊摔 http://blog.csdn.net/jiangyuze831/article/details/41445003 果然我除了抄代码什么也不会......树分块之类的东西完全不会计算复杂度..... 似乎upper_bound非常浪费时间..所以更改的时候直接循环查找不然会超时...... static这种东西不要胡乱用......如果在后面直接赋值会赋值不上........ 看代码就能

数据结构(树链剖分,堆):HNOI 2016 network

2215. [HNOI2016]网络 ★★★☆   输入文件:network_tenderRun.in   输出文件:network_tenderRun.out   简单对比时间限制:2 s   内存限制:128 MB [题目描述] [输入格式] [输出格式] [样例输入1] 13 23 1 2 1 3 2 4 2 5 3 6 3 7 4 8 4 9 6 10 6 11 7 12 7 13 2 1 0 8 13 3 0 9 12 5 2 9 2 8 2 2 0 10 12 1 2 2 1 3 2

Bzoj 2789: [Poi2012]Letters 树状数组,逆序对

2789: [Poi2012]Letters Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 278  Solved: 185[Submit][Status][Discuss] Description 给出两个长度相同且由大写英文字母组成的字符串A.B,保证A和B中每种字母出现的次数相同. 现在每次可以交换A中相邻两个字符,求最少需要交换多少次可以使得A变成B. Input 第一行一个正整数n (2<=n<=1,000,000),表示字符串的长度

BZOJ 2243 染色(树链剖分好题)

2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MB Submit: 7971  Solved: 2990 [Submit][Status][Discuss] Description 给定一棵有n个节点的无根树和m个操作,操作有2类: 1.将节点a到节点b路径上所有点都染成颜色c: 2.询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”.“222”和“1”. 请你写一个程序依