题解 P4964 【绫小路的特别考试】

· · 题解

〇、暴力

首先这题有一个比较好想的暴力:对于每对可以传递答案的同学连单向边,然后从每个可以独立作出的同学开始dfs。

这个暴力主要有两个问题:

  1. 边数太多,最多可以达到 O(n^2) 的级别。

  2. 每次询问都要dfs一次。

下面将分别进行讲解:

一、连边

其实对于每个同学,只需要向左右两边最近的能够接收到的同学各连一条边即可。

不妨令当前同学为第 u 位同学,他左边最近的能够接收到他的广播的同学为 l_u(l_u=\max\{v|v<u,v+d_v\ge u\}).

若存在比 l_u 更远的一位同学 p(p<l_u,p+d_p\ge u) 能够接收到 u 的广播,那么 p 一定能够接收到 l_u 的广播,而 l_u 又能接收到 u 的广播。

所以,u 向左只用连 (u,l_u) 这条边即可。向右是类似的。一共只要连不到 2n 条边。

说得好,怎么实现呢?

用一个栈,从左往右扫一遍,如果栈顶无法接收到当前同学的广播就弹出,若栈非空就向栈顶连边,然后将当前同学入栈。然后再从右往左扫一遍就好了。

二、单次dfs

注:这里的单次dfs不是指只dfs一次,而是指所有的dfs过程总复杂度为 O(n)

每次询问的时候dfs的起点都是 w_i 中最大的一些,所以先对 w_i 排序。

然后按顺序依次进行dfs就可以啦!

停!怎么dfs?怎么回答询问?

从大到小枚举 x,每次把 w_i=x 的点作为起点dfs,然后记录每个询问 x 的答案,就可以回答询问啦!

你当这题没有修改吗???

事实上,修改造成的影响只有 w_c<xw_c\ge x 两种,所以分两种情况,ans[0][x] 表示 w_c<x 时询问 x 的答案,ans[1][x] 表示 w_c\ge x 时询问 x 的答案。计算 ans[0][x] 时跳过 c,计算 ans[1][x] 时先以 c 为起点dfs再dfs其它点。

总复杂度:O(n+q)

等等?排序呢???

值得注意的是,vector/前向星常数可能较大。由于每个点最多只连两条边,可以用两个数组分别存向左/向右的边。同时最好不要用vector/前向星来作为桶排序的容器。 当然,~~ctr非常良心~~,实测前向星存图+前向星桶排~~只需~~$2.7s$,是可过的。两个数组存图+`std::sort`也只需 $1.8s$。 ### 参考代码: ```cpp #include <cstdio> #include <cstring> const int N=2000010; void dfs(int u); unsigned long long seed; int n, m, c, mfq, mind, maxd, k, w[2000001], d[2000001]; int sta[N],top,tot,ans[2][N],l[N],r[N],ord[N],cnt[N]; bool vis[N]; inline int randInt() { seed = 99999989 * seed + 1000000007; return seed >> 33; } void generate() { for (int i = 1; i <= n; i++) { w[i] = randInt() % n; } for (int i = 1; i <= n; i++) { d[i] = randInt() % (maxd - mind + 1) + mind; } } void getOperation(int lastans, int &opt, int &x) { if ((0ll + randInt() + lastans) % mfq) { opt = 1; } else { opt = 2; } x = (0ll + randInt() + lastans) % n; } int main() { int i,j,lxl; scanf("%d %d %d", &n, &m, &c); scanf("%llu %d %d %d %d", &seed, &mind, &maxd, &mfq, &k); generate(); for (int i = 1; i <= k; i++) { int p, t; scanf("%d %d", &p, &t); d[p] = t; } lxl=w[c]; //lxl表示当前绫小路的能力值 for (i=1;i<=n;++i) //连边 { while (top&&sta[top]+d[sta[top]]<i) { --top; } if (top) { l[i]=sta[top]; } sta[++top]=i; } top=0; for (i=n;i>=1;--i) { while (top&&sta[top]-d[sta[top]]>i) { --top; } if (top) { r[i]=sta[top]; } sta[++top]=i; } for (i=1;i<=n;++i) //计数排序 { ++cnt[w[i]]; } for (i=n-1;i>=0;--i) { cnt[i]+=cnt[i+1]; } for (i=1;i<=n;++i) { ord[--cnt[w[i]]]=i; } for (i=n-1,j=0;i>=0;--i) //计算ans[0][i] { while (j<n&&w[ord[j]]==i) { if (ord[j]!=c) { dfs(ord[j]); } ++j; } ans[0][i]=tot; } tot=0; memset(vis,false,sizeof(vis)); dfs(c); for (i=n-1,j=0;i>=0;--i) //计算ans[1][i] { while (j<n&&w[ord[j]]==i) { if (ord[j]!=c) { dfs(ord[j]); } ++j; } ans[1][i]=tot; } int lastans = 0, finalans = 0; for (i = 1; i <= m; i++) { int opt, x; getOperation(lastans, opt, x); if (opt == 1) { finalans = (finalans * 233ll + ans[lxl>=x][x]) % 998244353; lastans = ans[lxl>=x][x]; } else { lxl=x; } } printf("%d\n", finalans); return 0; } void dfs(int u) { if (vis[u]||u==0) { return; } vis[u]=true; ++tot; dfs(l[u]); dfs(r[u]); } ```