题目链接

题目

题目描述

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

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

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

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。

这道题分几步走:

  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;
}

NC14501 大吉大利,晚上吃鸡!的更多相关文章

  1. GMA Round 1 大吉大利,晚上吃鸡

    传送门 大吉大利,晚上吃鸡 新年走亲访友能干点啥呢,咱开黑吃鸡吧. 这里有32个人,每个人都可能想玩或者不想玩,这样子一共有$2^{32}$种可能.而要开黑当然得4人4人组一队(四人模式),所以说如果 ...

  2. [BZOJ5109]大吉大利,晚上吃鸡!

    [BZOJ5109]大吉大利,晚上吃鸡! 题目大意: 一张\(n(n\le5\times10^4)\)个点\(m(m\le5\times10^4)\)条边的无向图,节点编号为\(1\)到\(n\),边 ...

  3. 【BZOJ5109】[CodePlus 2017]大吉大利,晚上吃鸡! 最短路+拓扑排序+DP

    [BZOJ5109][CodePlus 2017]大吉大利,晚上吃鸡! Description 最近<绝地求生:大逃杀>风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏.在游戏 ...

  4. bzoj5109: [CodePlus 2017]大吉大利,晚上吃鸡!

    Description 最近<绝地求生:大逃杀>风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏.在游戏中,皮皮 和毛毛最喜欢做的事情就是堵桥,每每有一个好时机都能收到不少的快 ...

  5. 「CodePlus 2017 11 月赛」大吉大利,晚上吃鸡!(dij+bitset)

    从S出发跑dij,从T出发跑dij,顺便最短路计数. 令$F(x)$为$S$到$T$最短路经过$x$的方案数,显然这个是可以用$S$到$x$的方案数乘$T$到$x$的方案数来得到. 然后第一个条件就变 ...

  6. BZOJ5109 CodePlus 2017大吉大利,晚上吃鸡!(最短路+拓扑排序+bitset)

    首先跑正反两遍dij求由起点/终点到某点的最短路条数,这样条件一就转化为f(S,A)*f(T,A)+f(S,B)*f(T,B)=f(S,T).同时建出最短路DAG,这样图中任何一条S到T的路径都是最短 ...

  7. 「CodePlus 2017 11 月赛」大吉大利,晚上吃鸡!

    n<=50000,m<=50000的图,给s和t,问有多少点对$(a,b)$满足 嗯. 不会. 首先最短路DAG造出来,然后两个条件转述一下:条件一,$N_a$表示从s到t经过a的路径,$ ...

  8. [Code+#1]大吉大利,晚上吃鸡!

    输入输出样例 输入样例#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 输出样例#1: 6 输入样例#2: 5 5 1 4 1 2 1 1 3 ...

  9. [BZOJ5109/CodePlus2017]大吉大利,晚上吃鸡!

    Description 最近<绝地求生:大逃杀>风靡全球,皮皮和毛毛也迷上了这款游戏,他们经常组队玩这款游戏.在游戏中,皮皮和毛毛最喜欢做的事情就是堵桥,每每有一个好时机都能收到不少的快递 ...

  10. luogu4061 大吉大利,晚上吃鸡!

    链接 最短路径\(dag\),一道好题. 题目大意:求一张图中满足下列要求的点对\((i,j)\)数量: 所有最短路径必定会经过 \(i\) 点和 \(j\) 点中的任意一点. 不存在一条最短路同时经 ...

随机推荐

  1. ifram父页面、子页面元素及方法的获取调用

    page1 父页面 <div id="ifram" class="parent1"> <iframe frameborder="0& ...

  2. 动词时态=>2.动作的时间状态结合

    动作和时间结合 现在的四种时态 现在进行时态 对于 现在这个时间点,这个 动作 还在进行当中 例如:我现在正在喝水 现在完成时态 对于 现在这个时间点,这个 动作 已然完成 例子:我现在已经喝完了水 ...

  3. Selenium4+Python3系列(五) - 多窗口处理之句柄切换

    写在前面 感觉到很惭愧呀,因为居然在Selenium+Java系列中没有写过多窗口处理及句柄切换的文章,不过也无妨,不管什么语言,其思路是一样的,下面我们来演示,使用python语言来实现窗口句柄的切 ...

  4. Python基础部分:10、数据类型的内置方法和字符编码

    目录 一.数据类型内置方法 1.字典dict内置方法 1.1.类型转换 2.字典必须要掌握的方法 2.1.取值方式 2.2.修改内部数据值 2.3.删除数据 2.4.统计字典中键值对个数 2.5.字典 ...

  5. chrome工具调试

    项目调试的困境 程序开发总会遇到各种各样的问题,为什么实际结果和预期结果不一致? 这个时候如果能深入程序内部抽丝剥茧去一探究竟再好不过! 而chrome工具是前端开发的杀手锏,经常听到的一句话是: 出 ...

  6. 【题解】CF631B Print Check

    题面传送门 解决思路: 首先考虑到,一个点最终的情况只有三种可能:不被染色,被行染色,被列染色. 若一个点同时被行.列染色多次,显示出的是最后一次被染色的结果.所以我们可以使用结构体,对每一行.每一列 ...

  7. C#使用不安全指针来操作bitmap

    C#允许通过不安全指针实现像C++一样操作指针,这个速度是最快的.下面这个例子是修改一幅RGB图像的每个像素值,速度很快,测试一张2592*1944的彩色图像,只需要几ms就能够全部遍历. /// & ...

  8. C#使用MathNet库来对进行曲线拟合

    下面是用来求取一条直线和一条拟合曲线交点的代码 /// <summary> /// 拟合曲线所筛选的点的个数 /// </summary> const int CurveNum ...

  9. scrapy框架命令

    scrapy startproject #创建scrapy项目 scrapy genspider test www.baidu.com #在项目下的spider目录下生成爬虫文件 test爬虫名称 w ...

  10. linux server设置开机自动连接WIFI

    1.前言 之前买了一个工控机,装过几个OS(linux 发行版),但是一直没有细研究过流程,只是停留在能用就不管了,工控机自带无线网卡(和俩个有线网口),所以这篇文章好好介绍如何开机自动连接WIFI( ...