[CF416E]President‘s Path

220 篇文章 2 订阅
122 篇文章 0 订阅

题目

传送门 to luogu

题意概要
给出 n n n 个点 m m m 条边的简单无向图, 求出每一对 s , t    ( 1 ⩽ s < t ⩽ n ) s,t\;(1\leqslant s<t\leqslant n) s,t(1s<tn) 的所有最短路的边集的 并集 的大小。即:有多少条边 可能 s , t s,t s,t 最短路上的边。

注意,你要输出 n ( n − 1 ) 2 n(n-1)\over 2 2n(n1) 个数字,每个数字可能高达 m m m

数据范围与提示
2 ⩽ n ⩽ 500 2\leqslant n\leqslant 500 2n500 0 ⩽ m ⩽ n ( n − 1 ) 2 0\leqslant m\leqslant \frac{n(n-1)}{2} 0m2n(n1)

思路

为什么是蓝题?我觉得这道题很难啊……或者我脖子上的东西不太好使。

最初考虑枚举起点,这是 O ( n ) \mathcal O(n) O(n) 的。建出最短路图,然后用递推, f ( x ) = ∑ ⟨ y , x ⟩ f ( y ) f(x)=\sum_{\langle y,x\rangle}f(y) f(x)=y,xf(y) 。但是这样是会算重的!因为这是 DAG \texttt{DAG} DAG,而不是树。

然后又考虑用 b o o l \tt bool bool 数组存下每个点的可能边。没错,一个 O ( n m ) \mathcal O(nm) O(nm) 的数组。然后将其沿着最短路图上的边 “传导” 。由于最短路图上可能有 O ( m ) \mathcal O(m) O(m) 条边,每次转移是 O ( m ) \mathcal O(m) O(m) 的,于是时间复杂度应为 O ( m 2 ) \mathcal O(m^2) O(m2) 的……

突然灵机一动,从上面的做法中找到了优化点。回忆上面的过程,对于一条边,将其到达端点上打一个标记,然后往后推。这就是说,如果两条边的末端点相同,带来的影响是一样的

所以上面的 b o o l \tt bool bool 数组可以对点进行标记,表示第 x x x 点作为末端点的所有边是否已经被统计到。所以变成了 O ( n 2 ) \mathcal O(n^2) O(n2) 的数组。此时大力后推,就是 O ( n m ) \mathcal O(nm) O(nm) 的复杂度了。再算一算每个点作为结束端点的边数量即可。

怎么求出每个点作为结束端点的边数量?直接 F l o y d \rm Floyd Floyd 求最短路,然后可以使用距离数组进行判断啦。

某种意义上来说,我是对动态规划进行了优化,而不是图论做法

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <bitset>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 505;
const int MaxM = MaxN*MaxN;
int n, m;

struct Edge{
	int to, nxt, val;
	Edge(){ }
	Edge(int T,int N,int V){
		to = T, nxt = N, val = V;
	}
};
Edge e[MaxM];
int head[MaxN], cntE;
/** @param save If true, keep origin. */
void clear_graph(bool save = false){
	for(int i=1; i<=n; ++i)
		head[i] = -1;
	if(!save) cntE = 0;
}
/** @brief add a directed edge */
void addEdge(int a,int b,int c){
	e[cntE] = Edge(b,head[a],c);
	head[a] = cntE ++;
}

const int infty = (1<<30)-1;
int g[MaxN][MaxN], f[MaxN][MaxN];
int cnt[MaxN], ans[MaxN];

int main(){
	n = readint(), m = readint();
	for(int i=1,a,b; i<=m; ++i){
		a = readint(), b = readint();
		g[a][b] = g[b][a] = readint();
	}
	for(int i=1; i<=n; ++i)
	for(int j=1; j<=n; ++j)
		if(g[i][j] || i == j)
			f[i][j] = g[i][j];
		else f[i][j] = infty;
	for(int k=1; k<=n; ++k)
	for(int i=1; i<=n; ++i)
	for(int j=1; j<=n; ++j)
		f[i][j] = min(f[i][j],f[i][k]+f[k][j]);
	for(int i=1; i<n; ++i){
		for(int j=1; j<=n; ++j)
			cnt[j] = ans[j] = 0;
		for(int k=1; k<=n; ++k)
		for(int j=1; j<=n; ++j)
			if(g[k][j]) // 存在这条边
			if(f[i][j] == f[i][k]+g[k][j])
				++ cnt[j]; // 以 j 结尾
		for(int k=1; k<=n; ++k)
		for(int j=i+1; j<=n; ++j)
			if(f[i][j] == f[i][k]+f[k][j]){
				ans[j] += cnt[k];
			}
		for(int j=i+1; j<=n; ++j)
			printf("%d ",ans[j]);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值