LGP6628 [PUTS 2020-B] 丁香之路 学习笔记

LGP6628 [PUTS 2020-B] 丁香之路 学习笔记

Luogu Link

题意简述

给定一个 \(n\) 个结点的无向图。对于任意两个不同结点 \(i,j\),其间都有一条费用为 \(|i-j|\) 的边。其中有 \(m\) 条边是关键边。

对于从 \(1\)\(n\) 的每一个结点 \(i\),求从 \(s\) 出发,最终到达 \(i\) 且遍历所有关键边至少一次的最小费用。

\(n\le 2500\)

做法解析

以下讨论皆指无向图。

“遍历所有边至少一次”之类的条件让我们想到欧拉回路,但欧拉回路的定义是“经过每条边恰好一次的回路”,可现在题目要最小化的东西既不“每条”,又不“恰好”,更不“回路”。所以我们尝试对此题做一些转化,把它往欧拉回路上靠。

先考虑单个询问,设当前考虑的终点为 \(t\)。我们要求的应当是个以 \(s\) 为起点,以 \(t\) 为终点,包含 \(m\) 条关键边的……可重边集。意识到要考虑“可重”的边集非常重要。一方面在边集可重的情况下,任何一口气走下来的路径都是欧拉路径,另外把重复的边当成不同的个体,就可以说得要求每条边“恰好”走一次。

另外我们知道,欧拉路径的判定不太讨喜,它的起点和终点结点度数可以是奇数。所以我们假装连一条 \((s,t)\) 的虚拟边,就是说把 \(\text{deg}_s,\text{deg}_t\)。的奇偶性取反一次。现在我们要做的事情就是以尽量小的代价往这个可重边集里加边,使得它构成欧拉回路。

现在我们要判定的东西具象多了。一个边集(和它的点集)是欧拉回路,需要

  1. 每个点度数都是偶数;
  2. 所有点连通。

满足前者需要我们把度数为奇数的点相连。怎么做可以最小化代价呢?

诶题目里还有个性质!\(w_{i,j}=|i-j|\)。这个性质有什么意义呢?意义在于,如果我们想连上 \((i,j)\) 之间的边(\(i<j\)),不如连 \(\{(i,i+1),(i+1,i+2)\dots(j-1,j)\}\)\(i-j\) 条边,代价不变的同时还连上了更多个点。

我们把度数为奇的点两两配对,在每对点间这么连边,就可以以最小的代价调整好所有点的奇偶性。显然这一步代价不可能更小了。

现在,图缩成若干连通块。我们要把所有形如 \((i,j)\) 状的边拿出来做一个 Kruskal 状物。注意此时选出来的边为了维持奇偶性得走两遍,也就是代价要成是 \(2\)

为什么我说的不是“形如 \((i,i+1)\) 状的边?因为有些结点和我们的关键边都没关系,压根不用考虑。我们说的“相邻”是基于把关键边、起终点按结点编号排序后的“相邻”。

确定这么做贪心就一定对了?确定。首先显然只连 \((i,j)\) 状边是优的,然后,任何不把 \((i,j)\) 走两遍的方法都肯定得通过连别的一条 \((x,y),x<y\) 来确保奇偶性,显然这个 \((x,y)\) 不可能取什么东西比 \((i,j)\) 更优秀了。

另一方面也可以看出边的条数是 \(O(m+n)\) 级别的,所以说答案可以放心不会爆 int

总时间复杂度:\(O(n^2\log n+m)\)。这样一看 \(n\le 2500\) 这数据范围确实恰到好处。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=2505;
int N,M,S,X,Y,pdeg[MaxN],vis[MaxN];
int pans,fans[MaxN];
struct UnionFind{
    int n,ufa[2][MaxN],ct;
    void init(int x){n=x,ct=0;for(int i=1;i<=n;i++)ufa[0][i]=i;}
    int find(int u){return ufa[ct][u]==u?u:ufa[ct][u]=find(ufa[ct][u]);}
    void openup(int x){ct=x;for(int i=1;i<=n;i++)ufa[ct][i]=ufa[ct-1][i];}
    bool jurge(int u,int v){
        int fu=find(u),fv=find(v);
        if(fu==fv)return false;
        ufa[ct][fv]=fu;return true;
    }
}UniFd;
void addudge(int u,int v){
    pdeg[u]^=1,pdeg[v]^=1;
    UniFd.jurge(u,v),pans+=abs(u-v);
    vis[u]=vis[v]=1;
}
struct edge{int u,v,w;}E[MaxN];
bool cmpw(edge a,edge b){return a.w<b.w;}
int cdeg[MaxN],P[MaxN],pcnt,Q[MaxN],qcnt;
void solve(int t){
    fans[t]=pans,pcnt=qcnt=0;vis[t]^=2;
    for(int i=1;i<=N;i++){cdeg[i]=pdeg[i];if(vis[i])P[++pcnt]=i;}
    UniFd.openup(1),cdeg[S]^=1,cdeg[t]^=1;
    for(int i=1;i<=pcnt;i++)if(cdeg[P[i]]&1)Q[++qcnt]=P[i];
    for(int i=1;i<=qcnt;i+=2){
        fans[t]+=Q[i+1]-Q[i];
        for(int j=Q[i];j<Q[i+1];j++)UniFd.jurge(j,j+1);
    }
    for(int i=1;i<pcnt;i++)E[i]={P[i],P[i+1],P[i+1]-P[i]};
    sort(E+1,E+pcnt,cmpw);
    for(int i=1;i<pcnt;i++)if(UniFd.jurge(E[i].u,E[i].v))fans[t]+=E[i].w*2;
    vis[t]^=2;
}
int main(){
    readis(N,M,S),UniFd.init(N),vis[S]=1;
    for(int i=1;i<=M;i++)readis(X,Y),addudge(X,Y);
    for(int i=1;i<=N;i++)solve(i);
    for(int i=1;i<=N;i++)writip(fans[i]);
    return 0;
}
posted @ 2025-05-03 10:11  矞龙OrinLoong  阅读(16)  评论(0)    收藏  举报