0%

思路讲解

image

你可以发现,这个式子可以写成这样:

2c1+2c22^{c_1}+2^{c_2}

AC代码

心路历程(WA,TLE,MLE……)

思路讲解

当然,我们首先看到这道题目,会想到暴力。

但是暴力显然不行,先不管这个东西要怎么求,存储上,你存都存不下来。

既然这个暴力是不行的,我们就想到,这个图上面的路径太多了,有什么东西两点之间只有一条路径呢?其实就是树。

那么这种比较复杂的题目,特别是比较复杂的图论题目,一般都要划分阶段。

因此我们就要想,这个,如何把这节点一点一点的加入这个图当中?

可以的,兄弟,是可以的。

我们假设我们现在有一张图,我们可以先把到这张图距离为 i 的点加入进来,再把 i+1 的点加入进来,… 。

我们可以使用 kruskal 进行实现,kruskal 的基本过程就是按从小到大排序,加入到图中,使用并查集判环。

划分阶段其实也很简单,就是两张图把距离为 i 的点加入进来,停住。干一些事情,再继续加入距离为 i+1 的点,… 。

我们把一个中间状态摘出来看,如果原来的图是一样的(同构的),加入进来的点的并查集划分是一样的,那么两张图就是同构的。

这么说有点抽象。比如说蓝色的点是原来的图,红色的点是新加进来的点,不难发现,这两张图是同构的。因为只要并查集划分相同,新点之间的这个 dis 就是 i,老点到新点之间的 dis 也都是 i。

image

然后我们就来到了最后一个问题,怎么样快速判断( O(1)O(1) )两个图的并查集划分是相同的那?呃,其实不是那么难的。我们请出我们的老盆友,哈希。

一个联通块的签名可以理解为 有什么点。有什么点的话,我们可以给每个点整一个随机值(mt19937_64),然后一个联通块有什么点,就相当于其所有拥有的点的随机值的异或起来。

把这个好几个联通块组合(异或)起来,就可以得到目前的这个并查集划分状态,然后直接比较这个哈希值即可。

AC代码

https://qoj.ac/submission/1415583

心路历程(WA,TLE,MLE……)

merge 的逻辑比较复杂,容易写错,之后可以使用,Old,New,这样的命名,避免混淆。

1
2
3
4
5
6
7
8
9
10
11
void merge(ll a, ll b) {
ll faa = find(a), fab = find(b);
if (faa == fab) return;
S ^= mix(Sum[faa], siz[faa]);
S ^= mix(Sum[fab], siz[fab]);
fa[faa] = fab;
siz[fab] += siz[faa];
Sum[fab] ^= Sum[faa];
S ^= mix(Sum[fab], siz[fab]);
return;
}

思路讲解

dij 不能用,因为边太多了。

因为边权只有0 和 1,可以使用位集 bfs。

AC代码

AC

https://qoj.ac/submission/1411426

判断一下是否可以两次抵达,196ms

不判断两次抵达,1309ms

https://qoj.ac/submission/1410868

心路历程(WA,TLE,MLE……)

思路讲解

  1. 单条边的传输关系是单调的
    对一条漏水系数为 w 的边,从上游输入 x,到下游得到 x - ⌊x/w⌋;这是关于 x 的非减函数。
    因而“想让下游至少得到 need”的逆运算也是单调:最小的上游输入 x 满足 x - ⌊x/w⌋ ≥ need。你代码里的 get(need, w) 就是在做这件事(通过二分求最小可行 x)。

  2. 一次修边只能选择一条路径上的一条边
    每天只走“根→某个节点”的一条简单路径,因此修哪条边只会影响包含该边的那些路径。
    若我们要求所有**“弱点”节点**(即 R[u] < 目标值 mid 的节点)都至少收到 mid,那么被修的那条边必须同时位于它们各自的从根到该点路径上。
    这些路径的公共部分正是 根→(所有弱点的 LCA) 的路径。所以:

必要条件:可行的被修边一定在 root → anc 这条路径上,其中 anc 是所有 R[u]<mid 的节点的 LCA。

  1. 把“树下游的要求”折算为“对 anc 入水量的要求”
    如果我们暂时不修边,问:“从 anc 向下,把整棵以 anc 为根的子树都喂到 ≥ mid,需要 anc 处至少有多少输入?”
    由于每条边的传输是单调的,这个“所需的 anc 输入量”是单调的,可以二分出最小可行值 need_at_anc
    你用 dfs2(anc, candidate, mid) 来判定“给 anc 值为 candidate 时,子树是否都 ≥ mid”,并对 candidate 二分拿到最小的 need_at_anc。这一步只会遍历 anc 的子树,而不是整棵树,所以可控。

  2. 把 anc 的需求一路往上折算,看是否能借“修一条边”满足
    假设我们已经知道:只要 anc 处能拿到至少 need_at_anc 的输入,它子树就都 OK。
    现在看 root → anc 这条路径。我们在这条路径上自下而上地把需求往上游推

  • 当前在节点 node 处需要 need;让 fa=node 的父亲,边权 w=A[node]
  • 修边选在 (fa,node) 上,则 node 能直接拿到 R[fa](因为这条边不再漏水)。于是只要 R[fa] ≥ need 就已经满足了——这就是你代码里的“只要某一层祖先的 R[ancestor] ≥ need****,我们就能在这个祖先与其子之间修边,一步到位”的判定。
  • 否则,如果暂时不修这条边,就要通过这条漏水边把 need 推到父亲处,父亲的所需输入变为 get(need, w)。继续往上迭代,直到找到某个祖先 y 满足 R[y] ≥ 当前 need,那么在 (y, its child) 修边即可;若最终到根也没有成功,则该 mid 不可行。

这正是你 check(mid) 的核心:

  • 先找弱点集合 wp = { i | R[i] < mid },若空则已可行。

  • anc = LCA(wp 所有点)

  • anc 的所需输入二分最小可行 need_at_anc(用 dfs2 验证)。

  • need_at_anc 沿 anc→root 方向往上折算:

    • 若某处祖先的原始 R[ancestor] ≥ 当前 need → 可在它与子之间修边,成功;
    • 否则用 get(need, w) 把需求继续往上推;
    • 推到顶都不行则失败。

于是,一次修边能否让所有点 ≥ mid 被转换为上述流程的一个布尔判断。

AC代码

AC

https://www.codechef.com/viewsolution/1194966856

心路历程(WA,TLE,MLE……)