题解 CF - 101986 F Pizza Delivery (最短路+DAG必经点)

题解 CF - 101986 F Pizza Delivery (最短路+DAG必经点)

题目链接: https://codeforces.com/gym/101986

题意:

给你 N N N个点, M M M条边的有向图,无重边自环,每条边都有边权 w i w_i wi,设置起点为1,终点为2,现在要求对每条边询问一下:

如果将这条边的方向取反,即原来是 u → v u \rarr v uv现在修改为 v → u v\rarr u vu s → t s \rarr t st 的最短路有无变化

如果最短路增加了输出 “SAD”,不变输出"SOSO",减少了输出 “HAPPY”

数据范围: 2 ≤ N , M ≤ 1 0 5 , 2 ≤ w i ≤ 1 0 5 , 2 \le N,M \le 10^5 , 2 \le w_i \le 10^5, 2N,M105,2wi105,


思路:

题目要问与最短路的关系,那显然,我们得将最短路的图预处理出来,这是一个最短路DAG。

判断经过这条边 u → v u\rarr v uv的路径长度变化,我们可以知道,现在的长度是 d i s [ s ] [ v ] + d i s [ u ] [ t ] + w dis[s][v]+dis[u][t]+w dis[s][v]+dis[u][t]+w ,当然这条路径可能会经过原先 u → v u\rarr v uv 的这条边,不过没有影响,因为,如果经过了这条边,一定会使得长度变得更长。

所以当结果为"HAPPY"的时候非常容易判断。

但是"SAD"和"SOSO",就比较复杂了,因为如果这条边是在最短路DAG上的边,就有很多情况:

  1. 最短路DAG全都经过这条边,所以经过这条边的长度增加了最短路也增加
  2. 最短路DAG可以不经过这条边,所以经过这条边的长度增加了,最短路不变

这显然就是最短路中的必经边问题。我们可以使用支配树来求,也可以使用DP来求。

这里使用了DP。

  1. 在原图中按照拓扑序进行动态规划,求出起点 S S S到图中每个点 x x x的路径条数 f s [ x ] fs[x] fs[x]
  2. 在反图上再次按照拓扑序进行动态规划,求出每个点 x x x到终点 T T T的路径条数 f t [ x ] ft[x] ft[x]

显然, f s [ t ] fs[t] fs[t]表示从 S S S T T T的路径总条数。根据乘法原理:
1.对于一条有向边 ( x , y ) (x,y) xy,若 f s [ x ] ∗ f t [ y ] = = f s [ T ] fs[x]∗ft[y]==fs[T] fs[x]ft[y]==fs[T],则 ( x , y ) (x,y) xy是有 D A G DAG DAG S S S T T T的必经边。
2.对于一个点,若 f s [ x ] ∗ f t [ x ] = = f s [ T ] fs[x]∗ft[x]==fs[T] fs[x]ft[x]==fs[T],则 x x x D A G DAG DAG S S S T T T的必经点。

路径条数是一个指数级别的整数,通常超过了32位或64位整数的表示范围。受 H a s h Hash Hash思想的启发,我们可以把路径条数对一个大指数取模后再保存到 f s fs fs f t ft ft数组中。这样带来的后果是有较小的概率会产生误判。保险起见,若题目时限宽松,我们多选取几个质数,分别作为模数进行计算。

代码

#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(ll i = (ll)j;i <= (ll)k;i ++)
#define debug(x) cerr<<#x<<":"<<x<<endl
#define pb push_back

typedef long long ll;
typedef pair<ll,ll> pi;
const ll MAXN = (ll)1e5+7;
const ll INF = (ll)1e18+7;
const ll MOD = (ll)1e9+7;

struct Node{
    ll u,v,w;
    Node(ll u=0,ll v=0,ll w=0):u(u),v(v),w(w){}
}pro[MAXN];

vector<pi>G[3][MAXN];
vector<ll>V[2][MAXN];
ll dis[3][MAXN],dp[2][MAXN],deg[2][MAXN];
ll N,M,s,t;
priority_queue<pi> qu;
void djs(ll op) {
    while (!qu.empty()) qu.pop();
    rep(i,0,N) dis[op][i] = INF;

    dis[op][op] = 0;
    qu.push(make_pair(0,op));
    while (!qu.empty()) {
        pi k = qu.top();qu.pop();
        ll now = k.second;
        ll cos = -k.first;
        for(pi to:G[op][now]) {
            ll v = to.second;
            ll tmpCos = to.first;
            if (dis[op][v] > cos+tmpCos) {
                dis[op][v] = cos+tmpCos;
                qu.push(make_pair(-dis[op][v],v));
            }
        }
    }
}
queue<int> que;
void bfs(ll op) {
    while (!que.empty()) que.pop();
    rep(i,1,N) {
        if (deg[op][i]==0) {
            que.push(i);
            dp[op][i] = 1;
        }
    }
    while (!que.empty()) {
        ll k = que.front();que.pop();
        for(ll v:V[op][k]) {
            dp[op][v] = (dp[op][v]+dp[op][k])%MOD;
            if (--deg[op][v]==0) {
                que.push(v);
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin >> N >> M;
    rep(i,1,M) {
        ll u,v,w;
        cin >> u >> v >> w;
        G[1][u].pb(make_pair(w,v));
        G[2][v].pb(make_pair(w,u));
        pro[i] = Node(u,v,w);
    }
    s = 1,t = 2;
    djs(1);
    djs(2);
    rep(i,1,N) {
        for(pi to:G[1][i]) {
            ll u = i,v = to.second,w = to.first;
            if (dis[s][u]+w+dis[t][v]==dis[s][t]) {
                V[0][u].pb(v);
                V[1][v].pb(u);
                deg[0][v] ++;
                deg[1][u] ++;
            }
        }
    }
    bfs(0);
    bfs(1);

    rep(i,1,M) {
        ll u = pro[i].u,v = pro[i].v,w = pro[i].w;
        if (dis[s][v]+dis[t][u]+w < dis[s][t]) {
            cout << "HAPPY" << endl;
        }else if (dis[s][v]+dis[t][u]+w == dis[s][t]) {
            cout << "SOSO" << endl;
        }else {
            if (dis[s][u]+w+dis[t][v]==dis[s][t]) {
                if (dp[0][u]*dp[1][v]%MOD == dp[0][t]) {
                    cout << "SAD" << endl;
                }else {
                    cout << "SOSO" << endl;
                }
            }else {
                cout << "SOSO" << endl;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Best KeyBoard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值