刷题记录:牛客NC14501大吉大利,晚上吃鸡![一道图论好题]

传送门:牛客

题目描述:

游戏的地图可以抽象为一张 n 个点 m 条无向边的图,节点编号为 1 到 n ,每条边具有一个正整数的长度。
假定大魔王都会从 S 点出发到达 T 点( S 和 T 已知),并且只会走最短路,皮皮和毛毛会在 A 点和 B 点埋伏大魔王。
为了保证一定能埋伏到大魔王,同时又想留大魔王一条生路,皮皮和毛毛约定 A 点和 B 点必须满足:
1. 大魔王所有可能路径中,必定会经过 A 点和 B 点中的任意一点
2. 大魔王所有可能路径中,不存在一条路径同时经过 A 点和 B 点
K博士想知道,满足上面两个条件的 A,B 点对有多少个,交换 A,B 的顺序算相同的方案。
输入:
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

这是一道CodePlus 2017 11 月赛E题,一道图论的好题!!

此题难度较高且十分综合,远不止牛客上的三星难度,洛谷上为紫,鉴于其算法综合性和思维难度,我认为甚至可以上黑

注意,在观看下面题解之前,请确保自己已经熟练掌握 d i j k s t r a dijkstra dijkstra,拓扑排序,数位dp以及 b i t s e t bitset bitset等知识


首先对于牛客上的一些比较简单的AC代码做出解释:

这是因为本题的数据十分的水,甚至数据中只有一条最短路存在,这就导致了一些比较不靠谱的AC代码诞生


然后是本题的正解部分:

首先我们需要观察一下题面中的两个条件,不存在一条最短路径同时经过A,B.但是对于每一条最短路径又必须经过A,B中的一个点.为了叙述方便起见,我们定义F(x)为经过x点的最短路的数量.那么显然存在 F ( A ) + F ( B ) = F ( S ) = F ( T ) F(A)+F(B)=F(S)=F(T) F(A)+F(B)=F(S)=F(T),因为对于每一条最短路径来说,要么经过A点,要么经过B点.

那么对于我们的 F [ A ] F[A] F[A]数组来说,也就是经过一个点的最短路的数量,我们会发现,它其实是等价于s->A的所有最短路径的数量 ∗ * A->t的所有最短路径的数量,这里有几个简单的性质,那就是s->A和A->t的所有最短路径的长度显然是相等的(这个性质很容易证,这里就不赘述了).那么对于我们的 F [ B ] F[B] F[B]以及 F [ t ] , F [ s ] F[t],F[s] F[t],F[s]来说,显然和 F [ A ] F[A] F[A]一样满足这些

那么当我们得出 c n t cnt cnt和最短路径数量的关系之后,我们可以通过跑一边 d i j s k t r a dijsktra dijsktra计算出s到所有点的所有最短路径的数量(此时记作 c n t [ 0 ] [ v ] cnt[0][v] cnt[0][v]),同时反跑一边 d i j s k t r a dijsktra dijsktra计算出t到所有点的最短路径的数量(此时记作 c n t [ 1 ] [ v ] cnt[1][v] cnt[1][v]). 如果你对此有疑问,请熟悉一下dijsktra的运行原理和之前的解释

到目前位置,我们就可以求出所有的 F [ x ] F[x] F[x]的值了.并且我们的A,B需要满足上述关系,此时我们可以使用 m a p map map来映射我们的每一个F[x]所对应的所有节点编号,因为有多个节点编号,并且为了等会的统计方便,我们此时使用 b i t s e t bitset bitset来进行存储, b i t s e t bitset bitset中位置为1代表存在该节点.

此时此刻,假设我们枚举所有的A节点,我们就可以很方便的求出所有满足上述等式的B节点了.但是这仅仅只是条件1.我们还需要满足条件2,也就是需要满足我们的A和B并不在同一条最短路径中.

对于这个问题,我们可以求出经过该A节点的最短路中的其他节点,这一部分并不很难求,我们只需要使用数位dp+拓扑序的一点点原理就可以解决.对于所有的最短路,我们重新建一张图,此时的图就保证了是一个DAG,那么对于我们的这一张新图中,我们以拓扑序进行dfs遍历,对于我们的每一个点u来说,他的邻接点v->t所有的节点,u所在的最短路径必然都是经过的.因为u->v这条边自身就处于最短路径中的一条.对于这个关系,我们也可以使用 b i t s e t bitset bitset进行快速的存储和合并.
[注意,对于本题来说,博主直接建立反图来跑s->u的部分,当然也可以先存拓扑序进行遍历,方法有很多,使用一种就行]

至此,我们的这道题就接近尾声了.对于我们最终的答案,我们只要枚举每一个A节点,然后对于每一个A节点,我们先经过F数组的净化,得到可能可行的一些B节点,然后再经过第二个 b i t s e t bitset bitset进行判重,得到的,就是所有可行的B节点啦


注意:对于我们s和t可能不能联通,此时应该输出n ∗ * (n-1)/2,题目中并没有讲明,这是试+猜出来的

还有一件事,就是对于我们的最短路径数,是可以达到指数级别的,所以我们的longlong肯定是存不下的,所以我们可以对此进行大质数取模,对于大质数取模来说,很难产生冲突,至少我所取的1e9+7并没有产生冲突[doge]

下面是具体的代码部分(代码量较大):

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
#include <bitset>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define int long long
#define maxn 50050
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
/*-----------------------------------------------------------*/
const int mod=1e9+7;
int n,m,s,t;
struct Node{
	int v,w;
};
vector<Node>edge1[maxn];
int dis[2][maxn],vis[2][maxn];int cnt[2][maxn];
struct heapnode {
	int u,d;
	bool operator<(const heapnode &rhs) const {
		return d>rhs.d;
	}
};
void dij(int s,int num) {
	priority_queue<heapnode>q;
	for(int i=1;i<=n;i++) dis[num][i]=ll_maxn;
	dis[num][s]=0;cnt[num][s]=1;
	q.push({s,0});
	while(!q.empty()) {
		heapnode f=q.top();q.pop();
		int u=f.u;
		if(vis[num][u]) continue;
		vis[num][u]=1;
		for(int i=0;i<edge1[u].size();i++) {
			int v=edge1[u][i].v;
			if(dis[num][v]>dis[num][u]+edge1[u][i].w) {
				dis[num][v]=dis[num][u]+edge1[u][i].w;
				cnt[num][v]=cnt[num][u];
				q.push({v,dis[num][v]});
			}
			else if(dis[num][v]==dis[num][u]+edge1[u][i].w) {
				cnt[num][v]=(cnt[num][v]+cnt[num][u])%mod;
			}
		}
	}
}
/*-----------------------------------------------------------*/
vector<Node>edge2[maxn];int in[maxn];bitset<maxn>dp1[maxn];int vis1[maxn];
void dfs1(int u) {
	dp1[u][u]=1;vis1[u]=1;
	for(int i=0;i<edge2[u].size();i++) {
		int v=edge2[u][i].v;
		if(!vis1[v]) dfs1(v);
		dp1[u]|=dp1[v];
	}
}
vector<Node>edge3[maxn];int in2[maxn];bitset<maxn>dp2[maxn];int vis2[maxn];
void dfs2(int u) {
	dp2[u][u]=1;vis2[u]=1;
	for(int i=0;i<edge3[u].size();i++) {
		int v=edge3[u][i].v;
		if(!vis2[v]) dfs2(v);
		dp2[u]|=dp2[v];
	}
}
/*-----------------------------------------------------------*/
int F[maxn];map<int,bitset<maxn> >mp;
signed main() {
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;i++) {
		int u=read(),v=read(),w=read();;
		edge1[u].push_back({v,w});
		edge1[v].push_back({u,w});
	}
	dij(s,0);dij(t,1);
	if(cnt[0][t]==0) {
		cout<<n*(n-1)/2<<endl;
		return 0;
	}
	for(int i=1;i<=n;i++) {
		if(dis[0][i]+dis[1][i]==dis[0][t])
			F[i]=(cnt[0][i]*cnt[1][i])%mod;
		mp[F[i]][i]=1;
	}
	//存入最短路的边,形成一个DAG便于跑出每一个点的前驱后继
	for(int u=1;u<=n;u++) {
		for(int i=0;i<edge1[u].size();i++) {
			int v=edge1[u][i].v;
			if(dis[0][u]+dis[1][v]+edge1[u][i].w==dis[0][t]) {
				edge2[u].push_back({v,edge1[u][i].w});
				edge3[v].push_back({u,edge1[u][i].w});
				in[v]++;in2[u]++;
			}
		}
	}
	//存入最短路中每一个点的前面所有点和后面所有点
	for(int i=1;i<=n;i++) {
		if(in[i]==0) dfs1(i);
		if(in2[i]==0) dfs2(i);
	}
	int ans=0;
	for(int u=1;u<=n;u++) {
		ans+=((~(dp1[u]|dp2[u]))&mp[(F[t]-F[u]+mod)%mod]).count();
	}
	cout<<ans/2<<endl;
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值