[SCP-S 2026] 路短最 / path 题解

[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;
}
posted @ 2026-10-21 10:20  farenara_sayowell  阅读(5)  评论(0)    收藏  举报