NC14501 大吉大利,晚上吃鸡!

题目链接

题目

题目描述

最近《绝地求生:大逃杀》风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏。

在游戏中,皮皮和毛毛最喜欢做的事情就是堵桥,每每有一个好时机都能收到不少的快递。

当然,有些时候并不能堵桥,皮皮和毛毛会选择在其他的必经之路上蹲点。

K博士作为一个老年人,外加有心脏病,自然是不能玩这款游戏的,但是这并不能妨碍他对这款游戏进行一些理论分析,比如最近他就对皮皮和毛毛的战士很感兴趣。

游戏的地图可以抽象为一张 n 个点 m 条无向边的图,节点编号为 1 到 n ,每条边具有一个正整数的长度。

假定大魔王都会从 S 点出发到达 T 点( S 和 T 已知),并且只会走最短路,皮皮和毛毛会在 A 点和 B 点埋伏大魔王。

为了保证一定能埋伏到大魔王,同时又想留大魔王一条生路,皮皮和毛毛约定 A 点和 B 点必须满足:

\1. 大魔王所有可能路径中,必定会经过 A 点和 B 点中的任意一点
\2. 大魔王所有可能路径中,不存在一条路径同时经过 A 点和 B 点

K博士想知道,满足上面两个条件的 A,B 点对有多少个,交换 A,B 的顺序算相同的方案。

输入描述

第一行输入四个整数 n,m,S,T(\(1≤n≤5×10^4,1≤m≤5×10^4,1≤S,T≤n\)),含义见题目描述。
接下来输入 m 行,每行输入三个整数 u,v,w(\(1≤u,v≤n,1≤w≤10^9\))表示存在一条长度为 w 的边链接 u 和 v 。

输出描述

输出一行表示答案。

示例1

输入

7 7 1 7
1 2 2
2 4 2
4 6 2
6 7 2
1 3 2
3 5 4
5 7 2

输出

6

说明

合法的方案为 <2,3>,<2,4>,<4,3>,<4,5>,<6,3>,<6,5>。

备注

\(1≤n≤5×10^4,1≤m≤5×10^4,1≤w≤10^9\)

题解

知识点:最短路,拓扑排序,计数dp,DAG上dp。

这道题分几步走:

  1. 跑正反两次最短路,得到 \(f\) ,再求经过每个点 \(u\) 的最短路条数 \(ff[u] = f[0][u]\cdot f[1][u]\) ,不在最短路上的点应为 \(0\) 。同时,记录方案数到点的映射 \(mp[ff[u]][u] = 1\) 方便最后统计。注意不在最短路上的点也要统计,即 \(mp[0][u] = 1\) ,因为两个点可以有一个点不在最短路上,而另一个点通过了所有最短路。因为方案数本身过大,所以取了模,虽然很玄学,但能过。
  2. 根据第一步得到的 \(dis\) 新建一个最短路DAG图,即只包括最短路上的边且是单向的。在最短路图上跑一边拓扑排序得到拓扑序。对于每个点 \(u\) ,根据拓扑序求出可以从起点开始经过哪些点到达 \(tran[0][u]\),以及拓扑逆序求出经过哪些点到达终点 \(tran[1][u]\) 。这部分用bitset 实现刚刚好。
  3. 统计对于每个点 \(u\) 满足以下两个条件的点 \(v\)\(S\) 经过 \(u\)\(T\) 都不可能被经过,并且 \(ff[u] + ff[v] = ff[T]\) 。前者保证 \(u,v\) 不出现在一条最短路上满足条件2,后者保证 \(u,v\) 不重不漏的划分了所有路径,即从 \(S\)\(T\) 必定经过 \(u,v\) 满足条件1。

最后,因为交换A,B顺序算一个答案,所以统计的答案除以 \(2\)

注意,有可能 \(S\) 不能到达 \(T\) ,即 \(ff[T] = 0\) ,此时应该A,B可以是任意点,答案是 \(\frac{n(n-1)}{2}\) 直接输出。因为最短路图建不成,所以不能继续走后面的步骤,应该直接输出。

时间复杂度 \(O((n+m)\log m) + O(n+m) + O(nm)\)

空间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
#define ll long long

using namespace std;

template<class T>
struct Graph {
    struct edge {
        int v, nxt;
        T w;
    };
    int idx;
    vector<int> h;
    vector<edge> e;

    Graph(int n, int m) :idx(0), h(n + 1), e(m + 1) {}
    void init(int n) {
        idx = 0;
        h.assign(n + 1, 0);
    }

    void add(int u, int v, T w) {
        e[++idx] = edge{ v,h[u],w };
        h[u] = idx;
    }
};
const int N = 50007, M = 50007 << 1, mod = 1e9 + 7;
Graph<int> g(N, M), g2(N, M);
int n, m;

vector<vector<ll>> dis(2, vector<ll>(N));
vector<vector<int>> f(2, vector<int>(N));
vector<int> ff(N);
map<int, bitset<N>> mp;

void dijkstra(int st, vector<ll> &dis, vector<int> &f) {
    dis.assign(n + 1, 0x3f3f3f3f3f3f3f3f);
    vector<bool> vis(n + 1, false);
    struct node {
        int v;
        ll w;
        bool operator<(const node &a)const {
            return w > a.w;
        }
    };
    priority_queue<node> pq;

    dis[st] = 0;
    f[st] = 1;
    pq.push(node{ st,0 });
    while (!pq.empty()) {
        int u = pq.top().v;
        pq.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int i = g.h[u];i;i = g.e[i].nxt) {
            int v = g.e[i].v, w = g.e[i].w;
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                pq.push(node{ v,dis[v] });
                f[v] = f[u];
            }
            else if (dis[v] == dis[u] + w) {
                f[v] = (f[v] + f[u]) % mod;
            }
        }
    }
}

vector<int> topo;
void toposort() {
    vector<int> deg(n + 1, 0);
    queue<int> q;
    for (int i = 1;i <= g2.idx;i++) deg[g2.e[i].v]++;
    for (int i = 1;i <= n;i++) if (!deg[i]) q.push(i);
    while (!q.empty()) {
        int u = q.front();
        topo.push_back(u);
        q.pop();
        for (int i = g2.h[u];i;i = g2.e[i].nxt) {
            int v = g2.e[i].v;
            deg[v]--;
            if (!deg[v]) q.push(v);
        }
    }
}

bitset<N> tran[2][N];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int S, T;
    cin >> n >> m >> S >> T;
    for (int i = 1;i <= m;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        g.add(u, v, w);
        g.add(v, u, w);
    }

    //分别得到S,T到各个点的最短路和方案数
    dijkstra(S, dis[0], f[0]);
    dijkstra(T, dis[1], f[1]);

    if (f[0][T] == 0)//注意判断不连通的情况(等价于f[1][s]==0)
    {
        cout << 1LL * n * (n - 1) / 2 << '\n';//A可能情况*B可能情况/2(防止重复)
        return 0;
    }

    //在最短路上的点计算总条数,其他为0;并且统计一个方案数对应的点,方案数取模因为太大,不会被卡
    for (int u = 1;u <= n;u++) {
        if (dis[0][u] + dis[1][u] == dis[0][T])
            ff[u] = 1LL * f[0][u] * f[1][u] % mod;
        mp[ff[u]][u] = 1;
    }

    //用最短路上的边建图
    for (int u = 1;u <= n;u++) {
        for (int i = g.h[u];i;i = g.e[i].nxt) {
            int v = g.e[i].v, w = g.e[i].w;
            if (dis[0][u] + dis[1][v] + w == dis[0][T]) g2.add(u, v, w);
        }
    }

    //得到最短路遍历顺序
    toposort();

    //分别获得起点传递,终点逆传递
    for (int i = 0;i < n;i++) {
        int u = topo[i];
        tran[0][u][u] = 1;
        for (int j = g2.h[u];j;j = g2.e[j].nxt) {
            int v = g2.e[j].v;
            tran[0][v] |= tran[0][u];
        }
    }
    for (int i = n - 1;i >= 0;i--) {
        int u = topo[i];
        tran[1][u][u] = 1;
        for (int j = g2.h[u];j;j = g2.e[j].nxt) {
            int v = g2.e[j].v;
            tran[1][u] |= tran[1][v];
        }
    }

    //计算答案:两点不能在同一条最短路上,即g2中没有传递性;最短路至少通过一点,即两点方案数之和等于总方案数
    ll ans = 0;
    for (int u = 1;u <= n;u++) {
        ans += ((~(tran[0][u] | tran[1][u])) & mp[(ff[T] - ff[u] + mod) % mod]).count();
    }
    cout << ans / 2 << '\n';
    return 0;
}
posted @ 2023-01-04 22:41  空白菌  阅读(72)  评论(0编辑  收藏  举报