题解:P17724 [SCP-S 2026] 路短最 / path
题解:P17724 [SCP-S 2026] 路短最 / path
哎,我不会紫,我是 fvv。
(写题写了一半,看了一下题面)
欸,怎么变黑题了🫨。
思路
看到题目,意识到我们有两条手,于是考虑模拟题意。
使用一个优先队列维护二元组
这种方法能够直接求出答案,but 一个节点的距离可能被多次更新,每次更新又会向优先队列插入新的二元组,非常浪费😒。
既然题目只要求最终的计数器
先考虑原图是一棵树的情况🤔。
从节点
这意味着,如果原图没有环,问题就非常简单😆。
那么,额外的贡献来自哪里?
考虑一个简单环。当从环的一端开始扩展时,距离较大的候选状态会优先出队。如果两个方向上的候选状态距离相同,则编号较小的节点先出队,而它继续产生的新候选状态可能具有更大的距离。
这样,算法就可能先沿着环的一侧走得较远,再由另一侧传来的更短路径更新某些节点。这些节点可能再次产生有效出队,使得答案超过
因此,我们需要解决如何快速统计每个环造成的额外贡献,以及这些贡献如何通过环上的其他节点传递到整张图?
意识到我们有眼睛,于是考虑观察题面👀。
我们发现题目中提到了仙人掌图,于是我们可以利用仙人掌图的性质(每条边至多属于一个简单环),把环变成树。
我们以节点
对于一条连接节点
加上边
如果直接保留原来的树形结构,环上的两个方向会让 DP 的转移变得复杂🤯。因此,我们将路径上的节点
对于不在环上的普通树边,则保持原来的父子关系不变。
不过,直接将环改成一棵树会丢失两侧路径之间的距离差。因此,我们还需要给每个节点记录一个量
考虑上面的环。节点
两个方向的候选状态具有相同的初始距离,因此编号较小的一端会优先展开。假设
当
So,当
-
- 否则
h_{p_i} = 0 。
如果
-
- 否则
h_{p_i} = 0 。
这样,每个环上的节点就获得了相应的
接下来就是 DP 了。
If 一个子树完全不涉及环,那么它的每个节点只贡献一次,直接累计即可。
对于存在环的分支,则需要根据
为此,我们给每个节点
初始时,每个节点本身至少产生一次有效出队,因此初始化时需要令
合并时设
考虑
当然,只有
此外,当
合并完所有儿子后,再加上节点
但是我们可以发现,我们并不能对每个节点都独立维护一个 DP 数组,因为合并一个儿子时,可能需要遍历整个儿子的子树大小。
因此我们需要优化。
观察 DP 转移可以发现,合并一个儿子时,最主要的操作是将它的整个 DP 数组向后平移
如果能够让儿子的数组与父亲的数组共享一部分空间,那么这次平移就不必真的逐项执行。
于是,我们选择每个节点子树最大的儿子作为重儿子,记为
对于重儿子
这样,儿子的第
因此,向后平移的转移已经通过数组布局自然完成,不需要逐项复制。
对于需要保留在原位置的前缀部分,只需将重儿子的前
至于轻儿子嘛……由于它的数组不与父亲共享空间,仍然需要逐项合并😱。
完成整棵辅助树的 DP 后,根节点
因此,只需要将根节点的所有有效位置求和:
注意这里只统计根节点的 DP 数组。因为重链优化会让多个节点的 DP 数组共享全局数组中的部分位置,不能把每个节点的数组重复累加。
::::info[如果你需要正确性证明🧐]
首先,对于每条返祖边,仙人掌图的性质 can 保证它与 DFS 树上的对应路径构成唯一的简单环。按编号较小的一侧确定优先扩展方向后,利用两侧路径长度之差即可得到相应的偏移量
然后假设每个儿子的 DP 都正确。将儿子的贡献向父节点转移时,偏移后的位置记录了正常的贡献传递,而满足条件的原位置则记录了环产生的额外贡献。再加入节点自身的一次基本贡献,便能得到正确的父节点 DP。因此,由叶节点向根节点归纳,整棵树的 DP 都正确🤓☝️。
Finally,根节点包含所有原图节点的贡献。将其全部有效位置相加,得到的就是所有有效出队次数之和,也就是题目要求的
| 我能证明! |
|---|
注意到跑得还不慢,截止交审时是最优解第二页。
代码
::::success[代码]
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int MAXN = 500005;
const int MAXE = 1500005;
const int MAXF = 12000005;
int T, c, n, m, ans;
int head[MAXN], to[2 * MAXE], nxt[2 * MAXE], ecnt;
int dfn[MAXN], dep[MAXN], fa[MAXN], fedge[MAXN], fnew[MAXN], h[MAXN];
int path[MAXN], f[MAXF], tim, top;
int head2[MAXN], to2[MAXN], nxt2[MAXN], ecnt2;
int order[MAXN], s[MAXN], w[MAXN], l[MAXN];
int stk[MAXN], cedge[MAXN];
void mian()
{
cin >> n >> m;
ecnt = ans = 0;
for (int i = 1; i <= n; i++)
head[i] = dfn[i] = dep[i] = fa[i] = fedge[i] = fnew[i] = h[i] = 0;
for (int u, v, i = 1; i <= m; i++)
{
cin >> u >> v;
ecnt++;
to[ecnt] = v;
nxt[ecnt] = head[u];
head[u] = ecnt;
ecnt++;
to[ecnt] = u;
nxt[ecnt] = head[v];
head[v] = ecnt;
}
tim = top = 0;
stk[++top] = 1;
dfn[1] = ++tim;
cedge[top] = head[1];
while (top)
{
int u = stk[top];
int e = cedge[top];
if (e == 0)
{
top--;
continue;
}
cedge[top] = nxt[e];
int v = to[e];
if (v == fa[u])
continue;
if (!dfn[v])
{
dfn[v] = ++tim;
dep[v] = dep[u] + 1;
fa[v] = u;
fedge[v] = e;
fnew[v] = u;
h[v] = 0;
stk[++top] = v;
cedge[top] = head[v];
}
else if (dep[v] < dep[u])
{
int del = dep[u] - dep[v] + 1;
int x = u;
for (int i = del - 1; i >= 0; i--)
{
path[i] = x;
if (i > 0)
x = fa[x];
}
int a = path[1];
int b = u;
if (a < b)
{
for (int i = 1; i <= del - 1; i++)
{
int node = path[i];
int nh = 0;
if (i < del - 1 && 2 * i > del)
nh = 2 * i - del;
fnew[node] = v;
h[node] = nh;
}
}
else
{
for (int i = 1; i <= del - 1; i++)
{
int node = path[i];
int j = del - i;
int nh = 0;
if (j < del - 1 && 2 * j > del)
nh = 2 * j - del;
fnew[node] = v;
h[node] = nh;
}
}
}
}
ecnt2 = 0;
for (int i = 1; i <= n; i++)
head2[i] = 0;
for (int i = 2; i <= n; i++)
{
int p = fnew[i];
ecnt2++;
to2[ecnt2] = i;
nxt2[ecnt2] = head2[p];
head2[p] = ecnt2;
}
top = 0;
stk[++top] = 1;
int tot = 0;
while (top)
{
int u = stk[top--];
order[++tot] = u;
for (int e = head2[u]; e; e = nxt2[e])
{
int v = to2[e];
stk[++top] = v;
}
}
for (int i = 1; i <= n; i++)
{
s[i] = 1;
w[i] = 0;
}
for (int i = tot; i >= 1; i--)
{
int u = order[i];
s[u] = 1;
w[u] = 0;
for (int e = head2[u]; e; e = nxt2[e])
{
int v = to2[e];
s[u] += s[v];
if (w[u] == 0 || s[v] > s[w[u]])
w[u] = v;
}
}
int ptr = 1;
l[1] = ptr;
ptr += s[1];
for (int i = 1; i <= tot; i++)
{
int u = order[i];
for (int e = head2[u]; e; e = nxt2[e])
{
int v = to2[e];
if (v == w[u])
l[v] = l[u] + h[v];
else
{
l[v] = ptr;
ptr += s[v];
}
}
}
for (int i = 1; i < ptr; i++)
f[i] = 0;
for (int idx = tot; idx >= 1; idx--)
{
int u = order[idx];
if (w[u] != 0)
{
for (int i = 1; i <= min(h[w[u]], s[w[u]]); i++)
f[l[u] + i - 1] = f[l[w[u]] + i - 1];
}
for (int e = head2[u]; e; e = nxt2[e])
{
int v = to2[e];
if (v == w[u])
continue;
int *fv = f + l[v] - 1;
int *fu = f + l[u] - 1;
for (int i = 1; i <= s[v]; i++)
{
int val = fv[i];
if (val == 0)
continue;
if (i <= h[v])
fu[i] += val;
if (h[v] + i <= s[u])
fu[h[v] + i] += val;
}
}
f[l[u]] += 1;
}
for (int i = 1; i <= n; i++)
ans += f[l[1] + i - 1];
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> c >> T;
while (T--)
mian();
return 0;
}
::::