[SCP-S 2026] 路短最 / path 题解
神秘之 S 组模拟之神秘圆方树 DP。
先转化题意:有多少次有效出队,即代表有多少次某节点的最短路被更新。因为,一个被更新完的节点一定会在下一次被更新之前弹出,即一次更新只会贡献一次。
如果是一棵树结构的话,一个点只会更新一次,所以额外的更新一定来自环。
假设一个环,环顶是 \(u\),长度为 \(L\),且有 \(v_0 \dots v_{L-1},u\) 的顺序,保证 \(v_1 \le v_{L-1}\)。最先被更新的是 \(v_0\) 和 \(v_{L-1}\),其最短路成为了 \(d_u+1\)。然后由于距离相同时先取编号小的,会从 \(v_1\) 一直单方向更新到 \(v_{L-1}\),路上的点 \(v_i\) 的最短路更新为 \(d_{v_0}+i\)。但是还有另外的一个方向的更新,从 \(v_{L-1}\) 开始,将距离更新为 \(d_{v_0}+L-i\)。
除了最基本的一次之外,环上部分点的最短路显然被更新到了两次,即 \(\frac{L}{2} < i < L-1\) 的这些点。令这些点的较长路与较短路的差值 \(w_{v_i} = 2i-L\)。
我们并不用实际地把圆方树建出来,只要把环内所有点按上文顺序连在环顶的儿子即可。
但是,一个点的最短路更新会对其子树产生影响,所以我们要设计一个 DP。令 \(f_{u,x}\) 表示子树内距离已经稳定时,节点 \(u\) 的最短路减小了 \(x\) 之后,其子树内的节点会被更新多少次。
当 \(x\le w_v\),则新的较长路尚长与原来的最短路,所以只会产生一次更新。\(f_{u,x} \leftarrow f_{v,x}\)。
当 \(x > w_v\),则新的较长路已经长于原来的最短路,会产生 \(f_{v,x-w_v}\) 的更新以及 \(f_{v,w_v}\) 原本的更新。
再加上自己的一次,答案为 \(n + \sum_{v=2}^n f_{v,w_v}\),因为我们只统计了已经稳定后的额外更新。时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int o,T,n,m,ans;
const int N = 5e5+10,inf=1e15;
vector<int>g[N],e[N];
int w[N],dep[N],fa[N],col[N],pa[N],f[10005][10005];
void dfs(int u,int f){
fa[u]=f;
for(auto v:g[u]){
if(v==f)continue;
if(dep[v]==0){
dep[v]=dep[u]+1;
pa[v]=u;
dfs(v,u);
}else if(dep[v]<dep[u]){
vector<int>t;
for(int x=u;x!=v;x=fa[x]){
t.push_back(x);
}
t.push_back(v);
reverse(t.begin(),t.end());
int len=t.size();
if(t[1]>t.back())reverse(t.begin()+1,t.end());
for(int i=1;i<len;i++){
pa[t[i]]=v;
if(i>len/2&&i<len-1)w[t[i]]=2*i-len;
}
}
}
}
void dp(int u){
for(int i=0;i<=n;i++)f[u][i]=0;
for(auto v:e[u]){
dp(v);
for(int x=1;x<=n;x++){
if(x<=w[v]){
f[u][x]+=f[v][x];
}else{
f[u][x]+=f[v][x-w[v]]+f[v][w[v]];
}
}
}
for(int x=1;x<=n;x++)f[u][x]++;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>o>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dep[1]=1;
dfs(1,0);
for(int i=2;i<=n;i++){
e[pa[i]].push_back(i);
}
dp(1);
int ans=n;
for(int i=2;i<=n;i++){
ans+=f[i][w[i]];
}
cout<<ans<<endl;
for(int i=1;i<=n;i++){
dep[i]=fa[i]=pa[i]=w[i]=0;
g[i].clear();e[i].clear();
}
}
return 0;
}
考虑优化,考虑计算 \(f\) 的差分数组,观察转移式,从第 \(w_v+1\) 项开始,差分又重复了 \(1\) 到 \(w_v\) 的部分,最后再给 \(f_{u,1}\) 加一,答案是前 \(w_v\) 项之和。
考虑长剖优化 DP,令 \(siz_u\) 表示 \(u\) 的儿子里差分数组有效长度最长的儿子的长度,则 \(siz_u = \max(1,\max(siz_v+w_v))\)。
直接做,转移是简单的。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int o,T,n,m,ans;
const int N = 5e5+10,inf=1e15;
vector<int>g[N],e[N];
int w[N],dep[N],fa[N],col[N],pa[N],siz[N],lc[N];
void dfs(int u,int f){
fa[u]=f;
for(auto v:g[u]){
if(v==f)continue;
if(dep[v]==0){
dep[v]=dep[u]+1;
pa[v]=u;
dfs(v,u);
}else if(dep[v]<dep[u]){
vector<int>t;
for(int x=u;x!=v;x=fa[x]){
t.push_back(x);
}
t.push_back(v);
reverse(t.begin(),t.end());
int len=t.size();
if(t[1]>t.back())reverse(t.begin()+1,t.end());
for(int i=1;i<len;i++){
pa[t[i]]=v;
if(i>len/2&&i<len-1)w[t[i]]=2*i-len;
}
}
}
}
void dfs1(int u){
siz[u]=1;
for(auto v:e[u]){
dfs1(v);
if(siz[v]+w[v]>siz[u]||!lc[u]){
lc[u]=v;
siz[u]=siz[v]+w[v];
}
}
}
int p[3*N];
int *now=p,*f[N];
void dp(int u){
if(!lc[u]){
f[u][1]=1;
return;
}
f[lc[u]]=f[u]+w[lc[u]];
dp(lc[u]);
for(int i=1;i<=min(w[lc[u]],siz[lc[u]]);i++){
f[u][i]=f[lc[u]][i];
ans+=f[lc[u]][i];
}
for(auto v:e[u]){
if(v==lc[u])continue;
f[v]=now;
now+=(siz[v]+1);
dp(v);
for(int i=1;i<=siz[v];i++){
f[u][i+w[v]]+=f[v][i];
if(i<=w[v])f[u][i]+=f[v][i],ans+=f[v][i];
}
}
f[u][1]++;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>o>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dep[1]=1;
dfs(1,0);
for(int i=2;i<=n;i++){
e[pa[i]].push_back(i);
}
dfs1(1);
now=p;
f[1]=now;
now+=(siz[1]+1);
ans=n;
dp(1);
cout<<ans<<endl;
for(int i=1;i<=n;i++){
dep[i]=fa[i]=pa[i]=w[i]=lc[i]=siz[i]=0;
g[i].clear();e[i].clear();
}
memset(p,0,sizeof(p));
}
return 0;
}

浙公网安备 33010602011771号