题解:P17724 [SCP-S 2026] 路短最 / path
更好的阅读体验
比较深刻,比较困难。
首先有一个初步的观察,就是每个点每次被从大根堆中取出来的时候,最短路都会减小。这就意味着答案有着
把仙人掌以
更进一步地,我们发现假设一个环的环顶是
在仙人掌上由于存在环,所以问题比较复杂,考虑建立圆方树。于是考虑
再考虑方点的转移。一个方点
形式化地,我们记
- 如果
w_v = 0 或者w_v \ge i ,则当前点v 只产生一次更新,会直接让v 的最短路减少i ,即f_{u ,i} \leftarrow f_{v, i} 。 - 如果
0 < w_v < i ,则v 会产生两次更新,第一次更新会让v 最短路减少i-w_v ,第二次会让最短路减少w_v ,即f_{u, i} \leftarrow f_{v, w_v} + f_{v, i - w_v} 。
显然对于
接下来考虑优化这个 dp。我们充分发扬人类智慧,记
-
对于
u 为圆点或w_v = 0 的情形,有转移f_{u, i} \leftarrow f_{v, i} :容易发现和的差分等于差分的和,所以这个操作等价于g_{u, *} 和g_{v, *} 对位相加。 -
对于
u 为方点且w_v > 0 的情形,有转移f_{u, i} \leftarrow f_{v, \min(i, w_v)} + f_{v, \max(i - w_v, 0)} ,容易发现这个式子和上面讨论的两种情况等价。则这个时候我们先考虑h_i = f_{v, \min(i, w_v)} + f_{v, \max(i - w_v, 0)} 这个式子的差分数组,后面再加到g_{u, *} 上即可。容易发现,- 当
i \le w_v ,f_{v, \max(i-w_v, 0)} = 0 ,因此h_i - h_{i-1} = f_{v, i} - f_{v, i-1} = g_{v, i} 。 - 当
i > w_v ,f_{v, \min(i, w_v)} = f_{v, w_v} ,是常数。因此h_i - h_{i-1} = f_{v, i-w_v} - f_{v, i-w_v-1} = g_{v, i-w_v} 。
我们发现这种转移的实质是,把
g_v 的前w_v 项复制一遍再拼接到g_v 的前面,最后把拼接后的结果对位加到g_{u, *} 上! - 当
我们可以使用 map 维护每个
由于
g_{v, *} 的有效下标是子树大小量级的,且容易发现w_v 是环长量级的,在圆方树上等价于v 的兄弟个数。即要证,对于一棵树,记a_i 为i 子树大小,b_i 为i 的兄弟个数,则所有节点\min(a_i, b_i) 之和为O(n \log n) 。考虑将
i 的兄弟个数放缩为i 的所有兄弟的子树大小之和,则一个点的贡献,就是它的子树大小与其他兄弟的子树大小之和的\min 。容易发现假如一个点
u 在其祖先f 处产生了1 的贡献,则f 的父亲的子树大小相较于f 的子树大小必然至少翻倍。因此一个点至多产生O(\log n) 次贡献,总和必然是O(n \log n) 量级的。
至此我们在每个节点处维护 map,若
最后答案就是
至此问题得到解决,由于有
#include<bits/stdc++.h>
#define endl '\n'
#define N 1000006
using namespace std;
struct IO {} io;
using i64=long long;
int n,m,cnt,w[N],son[N];
int tot,dfs_clock,tp,low[N],dfn[N],stk[N];
vector<int> G[N],tG[N];
pair<int,i64> change[N];
void tarjan(int u)
{
dfn[u]=low[u]=++dfs_clock,stk[++tp]=u;
for(int v:G[u])if(!dfn[v])
{
tarjan(v),low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
int x=++tot; vector<int> vec={u};
do x=stk[tp--],vec.push_back(x); while(x!=v);
for(int i:vec)
tG[tot].push_back(i),tG[i].push_back(tot);
}
} else low[u]=min(low[u],dfn[v]);
}
struct DS {
map<int,i64> mp;
i64 tag,sum;
inline size_t size() {return mp.size();}
inline void clear() {mp.clear(),tag=sum=0;}
inline void add(int k,i64 x) {mp[k-tag]+=x,sum+=x;}
inline void update(int w) // 把 <= w 的在前面拼接一遍
{
tp=0;
for(auto it=mp.begin();it!=mp.end();it++)
if(tag+it->first<=w)change[++tp]={tag+it->first,it->second};
else break;
tag+=w;
for(int i=1;i<=tp;i++)add(change[i].first,change[i].second);
}
} t[N];
inline void merge(int u,int v) // v -> u
{
if(t[u].size()<t[v].size())
t[u].mp.swap(t[v].mp),swap(t[u].tag,t[v].tag),swap(t[u].sum,t[v].sum);
for(auto x:t[v].mp)t[u].add(x.first+t[v].tag,x.second);
}
void dfs(int u,int fa)
{
if(u<=n)
{
t[u].add(1,1);
for(int v:tG[u])if(v!=fa)dfs(v,u),merge(u,v);
return;
}
int pos=-1,sz=tG[u].size();
for(int i=0;i<sz;i++)
if(tG[u][i]==fa)pos=i; else dfs(tG[u][i],u);
cnt=0;
for(int i=pos+1;i<sz;i++)son[++cnt]=tG[u][i];
for(int i=0;i<pos;i++)son[++cnt]=tG[u][i];
if(son[1]>son[cnt])reverse(son+1,son+1+cnt);
for(int i=1;i<=cnt;i++)w[son[i]]=max(0,i*2-cnt-1);
w[son[cnt]]=0;
for(int i=1;i<=cnt;i++)
{
int v=son[i];
if(!w[v])merge(u,v); else t[v].update(w[v]),merge(u,v);
}
}
void solve()
{
tot=n=io.read(),m=io.read();
for(int i=1;i<=2*n;i++)
tG[i].clear(),G[i].clear(),dfn[i]=low[i]=0,t[i].clear();
for(int i=1,u,v;i<=m;i++)
u=io.read(),v=io.read(),G[u].push_back(v),G[v].push_back(u);
dfs_clock=tp=0,tarjan(1),dfs(1,0);
io.writeln(t[1].sum);
}
main()
{
io.read(); int T=io.read();
while(T--)solve();
return 0;
}