前言
这套题我们没有考,但是却也作为作业题写了。
怎么说啊。。。反正我是想不到思路的。
正文
题面:
题目描述
在上半套题中提到的那家公司在你的帮助之下服务成本大幅降低,于是这家公司开始快速发展,现在这家公司已经拥有了n名员工,分别从1到n编号。
为了方便管理。公司集体会议决定,可以将某个员工A任命为一个之前没有直接上司的员工B的直接上司,并且规定,当某个员工B收到并阅读完某个文件时,若B当前有直接上司A,则B需要继续将它交给A,A收到并阅读这个文件后,若当前A还有直接上司,那么还需将文件交给A的直接上司阅读,以此类推,直到某个阅读的人C没有直接上司,这时候C便会将该文件存档。
举个例子,比如B在t-1时刻收到了文件x,而A在t时刻成为了B的直接上司,那么A不会重新阅读B在t-1时刻收到的文件x。
这家公司又邀请你来帮忙,他们除了给你任命信息以及接收文件的信息之外,还会询问某一个人是否读过一个指定的文件,具体来说,现在这家公司按时间顺序给你m条信息/询问,为以下三种之一(一开始员工间无上下级关系):
1、(1操作,输入格式为1 B A)表示员工A成为了员工B的直接上司,保证在此之前B没有直接上司,并且上司关系显然不会构成环。
2、(2操作,输入格式为2 B)表示员工B收到了一份新文件,在下一个信息/询问之前,所有应该读到这份文件的人都会读到它。
3、(3操作,输入格式为3 A i)询问A是否读过这家公司按时间顺序收到的第i份文件,如果读过就输出"YES"字符串(不包括引号),否则输出"NO"(也不包括引号),保证在询问的时候第i份文件已存在。
输入格式
第一行两个正整数n,m,分别表示员工数和信息/询问数。
接下来m行按时间顺序给出信息/询问,每行都是上述三种输入格式之一。
输出格式
对每一条询问输出一行表示答案。
其实我挺想贴题解的,但是终究感觉不太道德(汗)
我们可以显然发现这题在线不好AC,因为我试过,加了streambuf快读的并查集暴力做法最后6个点直接RE,不知为何。
所以我们考虑离线处理所有操作。
1.首先把操作1离线下来建一棵树,注意建得是有向边,我建的是上司向下属的边。边权赋为当前时间(其实也是在此之前2操作的个数)。
2.把所有操作2用一个数组记录一下第i次文件是传给哪个人的,并将时间累计(你也可以以此为下标)。
3.将操作3先存起来。
然后预处理出链上的边权最大值,因为我们发现数据结构不能很好的维护这一类树上问题(其实可以,我也写了一半了),于是选择用简单的倍增法求解。
然后对于每个询问u,i,我们取出之前存的第i次询问的点v,求u,v的lca,这里发现如果u不是v的祖先就不可能拿到文件。
求u到v的边权最值,若时间大于现在的文件i,说明两点间路径上有边在文件传输后出现,所以u接收不到,反之则可以。
哎,蒟蒻原本想写个树剖练练手的,结果写到一半脑残了觉得树剖不能找到两点之间的边权最大只能维护有可减性的信息,然后写到一半弃了写倍增,还调了n年。其实感觉树剖会好些一些。
这就导致了这个lca用的是树剖,结果还是用倍增维护最大值的奇怪代码,跑的奇慢。
\(Code:\)
#include<bits/stdc++.h> #define N 300010 #define int long long using namespace std; int n, m; int fl[N] = {}, len = 0; struct rode { int y, next, z; }e[N << 1] = {}; int a[N] = {}; struct question { int x, y; }que[N] = {}; int si[N] = {}, d[N] = {}, hs[N] = {}, ff[N] = {}; int top[N] = {}, pr[N] = {}, tr[N] = {}; int f[N] = {}; int in[N] = {}; int read() { int s = 0, w = 1; char c = getchar(); while ((c<'0' || c>'9') && c != '-')c = getchar(); if (c == '-')w = -1, c = getchar(); while (c <= '9'&&c >= '0')s = (s << 3) + (s << 1) + c - '0', c = getchar(); return s * w; } inline void inc(int x, int y) { e[++len].y = y; e[len].next = fl[x]; fl[x] = len; ++in[y]; } int ti = 0, cnt = 0; int fie[N] = {}; int block;//倍增步数 int mx[N][35] = {}, z[N][35] = {}; //mx[i][j]就是i向上跳2^j步的最大边权值,z是倍增的数组 void dfs(int u, int fa) { si[u] = 1; int maxn = 0; for (int i = fl[u]; i; i = e[i].next) if (e[i].y != fa) { int v = e[i].y; d[v] = d[u] + 1; f[v] = u; z[v][0] = u;//初值 dfs(v, u); for (int j = 1; j <= block; ++j) { z[v][j] = z[z[v][j - 1]][j - 1]; mx[v][j] = max(mx[v][j - 1], mx[z[v][j - 1]][j - 1]); //倍增的求解每条链上的最大边权值 } si[u] += si[v]; if (si[v] > maxn)maxn = si[v], hs[u] = v; } } int vs = 0, hai = 0; void dfs1(int u, int k) { top[pr[tr[u] = ++vs] = u] = k; pr[vs] = a[u]; if (!hs[u])return; dfs1(hs[u], k); for (int i = fl[u]; i; i = e[i].next) if (e[i].y != f[u] && e[i].y != hs[u]) dfs1(e[i].y, e[i].y); }//处理top数组 int lca(int x, int y) { while (top[x] != top[y]) { if (d[top[x]] < d[top[y]])swap(x, y); x = f[top[x]]; } if (d[x] < d[y])swap(x, y); return y; }//树剖lca signed main() { freopen("manage.in", "r", stdin); freopen("manage.out", "w", stdout); n = read(); m = read(); block = log(n) / log(2); for (int i = 1; i <= n; ++i)f[i] = i; for (int i = 1; i <= m; ++i) { int op = read(); if (op == 1) { int x = read(), y = read(); //建图,暂时不管时间问题 inc(y, x);//注意单向边,尽管我也不知道你用双向边会发生什么。。。 mx[x][0] = ti;//把时间作为边权,初始化mx数组 } if (op == 2) { int x = read(); fie[++ti] = x;//fie[i]表示第i次文件的是传给谁的(第一个拿到的) } if (op == 3) { int x = read(), y = read(); que[++cnt] = { x,y }; //离线下所有询问 } } for (int i = 1; i <= n; i++) if (!in[i]) inc(0, i); //建立一个虚拟源点0,因为原图是一个森林 dfs(0, -1); dfs1(0, 0); //预处理 for (int i = 1; i <= cnt; ++i) { int u = que[i].x, v = fie[que[i].y]; int LCA = lca(u, v); if (LCA != u) { puts("NO"); continue; } //如果u本来就没有接收到这份文件,输出no int maxn = 0, x = v; for (int j = 30; j >= 0; --j) if (d[z[x][j]] >= d[LCA]) maxn = max(maxn, mx[x][j]), x = z[x][j]; //倍增法找u到v路径上的最大边权 //这样可以确认u能接收到这份文件 //如果路径上有边比文件晚出现,那u是接收不到文件的 if (maxn < que[i].y)puts("YES"); else puts("NO"); } return 0; }
大概就是这样了,我觉得代码里也讲的挺清楚的。
这是在博客园的第二篇题解,原本说好的在这发(实际上确实),不过我很久没写blog了。
都快联赛了,多复习吧