『学习笔记』最短路

P5304 [GXOI/GZOI2019] 旅行者

有向图,\(k\) 个关键点,求任意两个关键点之间最短路的最小值。多测。

\(n\le10^5,m\le5\times10^5,w\le2\times10^9\)

\(O(n\log^2n)\) 做法

注意到求两两之间最短路,考虑将关键点分为两个集合 \(A,B\),分别建立虚点 \(s,t\) 连接并跑最短路,一次最短路需 \(O(n \log n)\)

那么考虑如何分集合。同样看到两两不同,考虑二进制分解。因为每两个关键点编号不同,所以二进制下肯定至少有一位不相同。那么可以按照每一位二进制 \(0/1\) 分入两个集合,这样任意两个点之间一定有至少一次在不同的集合中。这样就只需要跑 \(O(\log n)\) 次最短路了。从每次 \(s\)\(t\) 的最短路中取最小值即可。

\(O(m+n\log n)\) 做法

分别在正反图上以关键点为起点跑一次最短路并记录每个点的最短路是从哪个关键点来的,然后枚举每条边 \((u,v,w)\) 作为两个关键点间最短路上的一条边,若正图上 from[u] 与反图上 from[v] 不同,即从一个关键点走最短路到 \(u\),经过 \((u,v,w)\) 这条边,再从 \(v\) 走最短路到达另一个关键点 \(v\),那么此时的答案是合法的。

注意多测清空,数组开大,\(\inf\) 开大,区分两张图。

记录详情

P6880 [JOI 2020 Final] オリンピックバス

有向带权图,每条边有一个 \(d_i\),可以用 \(d_i\) 的代价翻转第 \(i\) 条边。最多反转一条边,求 \(1\to n \to 1\) 的最短路。

一个暴力方法是先预处理与 \(1,n\) 相关的四个最短路以便计算答案,然后直接枚举每条边并翻转然后跑最短路求解。

跑最短路次数有点多,考虑如何减少。对于每一条边,当且仅当它在最短路树上,翻转后才有可能影响最短路答案。于是可以先求出最短路树,然后对每一条最短路树上的边跑 dij,加上 \(d_i\) 算答案。

  • \(n\) 较小,可用邻接矩阵方便翻转。对于重边,可以取最小值,但也要记录次小值以方便翻转。
  • 稠密图,dij 暴力比堆优化快。
  • dij 不要写多个,用参数确定怎么跑。其中 dist 可传指针。
  • 局部变量,不要开全局,我因此调了 \(2h\)

记录详情

P7515 [省选联考 2021 A 卷] 矩阵游戏

\(b_{i,j}=a_{i,j}+a_{i,j+1}+a_{i+1,j}+a_{i+1,j+1}\),给出 \(0 \le b_{i,j} \le 4 \times 10^6\),还原出 \(0 \le a_{i,j} \le 10^6\)。多测。

考虑将 \(a_{1,i}\)\(a_{i,1}\) 全部填 \(0\),然后递推出所有 \(a_{i,j}\)。问题是 \(a_{i,j} > 10^6\)。那么想办法匀一下?

对于一个田字格,显然将同一行或同一列两个元素同时加减一个数,\(b_{i,j}\) 是不变的。

考虑 \(0 \le a_{i,j}\pm row_i\pm col_j \le 10^6\),使得两个符号不同。可以根据 \(i,j\) 奇偶性决定(详细见代码)。

注意到一个等式中有两个符号相反的未知数,差分约束不就来了吗。

// 已暴力构造出 a[i][j]
for(int i=1; i<=n; i++)
    for(int j=1; j<=m; j++){
        // 0 <= a[i][j] + -1^(i+1)row[i] + -1^(j)col[j] <= 10^6
        if(i+j&1){ // i,j奇偶性不同
            add(i,j+n,a[i][j]); // c[j]<=r[i]+a[i][j]  0<=r[i]-c[j]+a[i][j]
            add(j+n,i,1e6-a[i][j]); // r[i]<=c[j]+1e6-a[i][j]     r[i]-c[j]+a[i][j]<=1e6
        }else{
            add(j+n,i,a[i][j]); // r[i]<=c[j]+a[i][j]    0<=-r[i]+c[j]+a[i][j]
            add(i,j+n,1e6-a[i][j]); // c[j]<=r[i]+1e6-a[i][j]     -r[i]+c[j]+a[i][j]<=1e6
        }
    }

记录详情

P4926 [1007] 倍杀测量者

\[x_{a_i} \ge (k_i-t) \times x_{b_i}\ \ \&\ \ (k_i+t)\times x_{a_i}>x_{b_i} \]

给出不等式与一些 \(x_i\),求最大的 \(t\) 使不等式无解。

很明显的单调性。可以二分答案+差分约束,求最长路。

对于连边,虽然可以直接乘积,但是容易挂精度。考虑拆分化简:

\[x_{a_i} \ge (k_i-t) \times x_{b_i}\\ \downarrow\\ \log_2(x_{a_i})\ge\log_2(x_{b_i})+\log_2(k_i-t)\\\\ (k_i+t)\times x_{a_i}>x_{b_i}\\ \downarrow\\ \log_2(k_i+t)+\log_2(x_{a_i})>\log_2(x_{b_i}) \]

于是可以加法连边,对边权取 \(\log\)

const int N=1e5+5,inf=0x3f3f3f3f;
const double eps=1e-6;
struct edge{
    int to,nxt,tp;
    double k;
}e[N<<5];
int n,s,t;
int head[N],top;
int vis[N],cnt[N];
double dist[N];
int q[N<<4];

int spfa(double t){
    for(int i=0; i<=n; i++) dist[i]=-inf,cnt[i]=vis[i]=0;
    dist[n+1]=0,vis[n+1]=1;
    int l=1,r=1;
    q[1]=n+1;
    while(l<=r){
        int u=q[l++];
        vis[u]=0;
        for(int i=head[u]; i; i=e[i].nxt){
            int v=e[i].to;
            double w=e[i].k;
            if(e[i].tp==1) w=log2(w-t);
            if(e[i].tp==2) w=-log2(w+t);
            if(dist[v]<dist[u]+w){
                dist[v]=dist[u]+w;
                cnt[v]=cnt[u]+1;
                if(cnt[v]>=n+2) return 1;
                if(!vis[v]) q[++r]=v,vis[v]=1;
            }
        }
    }
    return 0;
}

int main(){
    n=read(),s=read(),t=read();
    for(int i=0; i<=n; i++) add(n+1,i,0,3);
    double l=0,r=10,ans=114514,mid;
    while(s--){
        // dist[u]>=(k-t)dist[v]    log2(dist[u])>=log2(dist[v])+log2(k-t)
        // (k+t)dist[u]>dist[v]     log2(dist[u])>log2(dist[v])-log2(k+t)
        int op=read(),u=read(),v=read();
        double k=read();
        add(v,u,k,op);
        if(op==1) r=min(r,k);
    }
    while(t--){
        int v=read(); double w=read();
        add(0,v,log2(w),3); // dist[v]>=dist[0]+log2(w)
        add(v,0,-log2(w),3); // dist[0]>=dist[v]-log2(w)
    }
    if(!spfa(0)) return puts("-1"),0;
    while(r-l>eps){
        mid=(l+r)/2.0;
        if(spfa(mid)) ans=mid,l=mid+eps;
        else r=mid-eps;
    }
    printf("%.6lf\n",ans);
    return 0;
}

记录详情

posted @ 2025-01-05 23:09  仙山有茗  阅读(17)  评论(0)    收藏  举报