首页 > 其他分享 >CF1650G 『Counting Shortcuts』 题解

CF1650G 『Counting Shortcuts』 题解

时间:2022-11-14 11:14:37浏览次数:78  
标签:Shortcuts 题解 短路 int text CF1650G 转移 dp dis

洛谷博客那里搬过来的(


图论专题本来打算先挑最简单的做,结果做了两个多小时(

题意就是让你找从起点 \(s\) 到终点 \(t\) 的最短路以及次短路个数,本题次短路长度指的是最短路长度 \(-1\)。

考虑 \(\text{DP}\)。

设 \(dp_{u,0/1}\) 为当前到了点 \(u\),\(0/1\) 是与 \(s\) 到 \(u\) 的最短路长度相差 \(0/1\) 的路径的条数。

手模几组样例容易得出转移方程。先跑一遍 \(\text{Dijkstra}\) 算出 \(s\) 到所有点的最短路 \(dis\),设当前点为 \(u\),下一个点为 \(v\)。则有:

  • 若 \(dis_u = dis_v\),则说明 \(v\) 的最短路并非 \(u\) 的最短路 \(+1\),而是 \(u\) 的最短路和 \(v\) 的最短路差 \(1\),则可 \(dp_{u, 0} \to dp_{v, 1}\)。

  • 若 \(dis_u = dis_v\),则说明 \(v\) 的最短路是可以由 \(u\) 转移过来的。于是v继承u的所有状态。即:\(dp_{u, 0} \to dp_{v, 0}\text{,}dp_{u, 1} \to dp_{v, 1}\)。

初态是 \(dp_{s, 0} = 1\),因为题目问的是最短路与次短路个数之和所以末态为 \(dp_{t, 0} + dp_{t, 1}\)。

至此就可以高高兴兴写出一个 \(\text{dfs}\) 代码了。

void dfs(int x) {
	if (x == hd)
		return ;
	vis[x] = true;
	for (re i = head[x] ; i ; i = e[i].nxt) {
		int v = e[i].v;
		if (vis[v] == true)
			continue;
		if (dis[v] == dis[x])
			Plus(dp[v][1], dp[x][0]);
		else if (dis[v] == dis[x] + 1)
			Plus(dp[v][0], dp[x][0]), Plus(dp[v][1], dp[x][1]);
		dfs(v);
	}
	vis[x] = false;
}

然后测一下前三个样例,哇,都过了。以为这题就要切了,测第四个样例结果发现输出 1204

显然是转移多了。考虑下面一张图:

image

按照写的 \(\text{dfs}\) 模一下发现点 \(4\) 和点 \(3\) 转移到点 \(5\) 的时候出现了问题:转移顺序。

比如转移顺序:\(2 \to 4 \to 5\),但是还有 \(2 \to 3 \to 4 \to 5\),发现在第一遍转移的时候已经转移给了点 \(5\),但是第二次转移的时候除了把新的值转移给了 \(5\),还把以前的值又转移了一遍。所以就转移多了。

考虑如何解决。既然你是重复转移了前一次的,那我对于每个点转移过了之后把他的 \(\text{dp}\) 值清空,下一次再来到这个点的时候就不会重复转移上一次的值了。

void dfs(int x) {
	if (x == hd)
		return ;
	vis[x] = true;
	for (re i = head[x] ; i ; i = e[i].nxt) {
		int v = e[i].v;
		if (vis[v] == true)
			continue;
		if (dis[v] == dis[x])
			Plus(dp[v][1], dp[x][0]);
		else if (dis[v] == dis[x] + 1)
			Plus(dp[v][0], dp[x][0]), Plus(dp[v][1], dp[x][1]);
		dfs(v);
	}
	dp[x][0] = dp[x][1] = 0;// 多加了这一句
	vis[x] = false;
}

交上去会发现 TLE on test #3,我不太清楚 \(\text{dfs}\) 常数大还是咋地,卡了半天常也过不去(

考虑换一种解决方法。发现重复转移的实质就是拿还没转移完毕的去更新其他的了,于是考虑如何让他转移完毕再去更新。

注意到点 \(u\) 所有的转移都是对于 \(dis_v = dis_u\) 或 \(dis_v = dis_u + 1\) 的 \(v\) 去转移,这启示我们按照 \(dis\) 对原图进行分层。对于每个点 \(u\) 先对于同层也就是 \(dis_v = dis_u\) 的点 \(v\) 进行转移,全部转移完毕后再去转移 \(dis_v = dis_u + 1\) 的点 \(v\) 即可。

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#define GMY (520 & 1314)
#define char_phi int
#define re register int
#define FBI_OPENTHEDOOR(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout)
#define N 2000005
#define M 2000005
#define P 1000000007
using namespace std;
inline void Fastio_setup() { ios :: sync_with_stdio(false); cin.tie(NULL), cout.tie(NULL); }
inline int MAX(int x, int y) { return ((x > y) ? (x) : (y)); }
inline int MIN(int x, int y) { return ((x < y) ? (x) : (y)); }
 
int n, m, star_cnt, yd, hd;
char vis[N];
int head[N], q[M<<3], dis[N];
int dp[N][2];
 
struct star { int v, nxt; };
struct Node {
	int x, d;
	
	friend bool operator < (Node A, Node B) { return A.d > B.d; }
};
 
struct star e[M<<1];
 
priority_queue<Node> heap;
vector<int> vec[N];
 
inline void star_add(int u, int v) { e[++ star_cnt].v=v, e[star_cnt].nxt=head[u], head[u]=star_cnt; }
inline void Clean() {
	star_cnt = 0; vec[0].clear();
	for (re i = 1 ; i <= n ; ++ i) {
		head[i] = dp[i][0] = dp[i][1] = 0;
		vec[i].clear();
	}
}
 
inline void Dijkstra() {
	int x = yd; for (re i = 1 ; i <= n ; ++ i) dis[i] = 0x3f3f3f3f, vis[i] = false;
	heap.push( (Node) {x, 0} ); dis[x] = 0;
	while (heap.empty() == false) {
		x = heap.top().x; heap.pop();
		if (vis[x] == true)
			continue;
		vis[x] = true;
		for (re i = head[x] ; i ; i = e[i].nxt) {
			int v = e[i].v;
			if (dis[v] > dis[x] + 1) {
				dis[v] = dis[x] + 1;
				if (vis[v] == false)
					heap.push( (Node) { v, dis[v] } );
			}
		}
	}
}
inline void Plus(int& who, int val) { who += val; if (who >= P) who -= P; }
 
inline void Search() {// 先更新0再更新1
	for (re i = 1 ; i <= n ; ++ i) {
		vis[i] = false;
		vec[dis[i]].emplace_back(i);
	}
	
	dp[yd][0] = 1;
	for (re dep = 0 ; dep <= n ; ++ dep) {
		for (auto x : vec[dep])
			for (re i = head[x] ; i ; i = e[i].nxt) {// 先转移同层的
				int v = e[i].v;
				if (dis[v] == dep)
					Plus(dp[v][1], dp[x][0]);
			}
		for (auto x : vec[dep])
			for (re i = head[x] ; i ; i = e[i].nxt) {// 再转移其他层的
				int v = e[i].v;
				if (dis[v] == dep + 1)
					Plus(dp[v][0], dp[x][0]), Plus(dp[v][1], dp[x][1]);
			}
	}
}


inline void work() {
	Clean();
	cin >> n >> m; cin >> yd >> hd;
	for (re i = 1, uu, vv ; i <= m ; ++ i)
		{cin >> uu >> vv; star_add(uu, vv), star_add(vv, uu);}
	
	Dijkstra();
	Search();// 也许算是bfs罢
	
	cout << (dp[hd][0] + dp[hd][1]) % P << '\n';
}
 
#undef int
// #define IXINGMY
char_phi main() {
	#ifdef IXINGMY
		FBI_OPENTHEDOOR(a, a);
	#endif
	Fastio_setup();
	int T; cin >> T;
	while (T --)
		work();
	return GMY;
}

\(\text{Hint}\):参考了 \(\text{Jerry-Black}\) 大佬的思路。

标签:Shortcuts,题解,短路,int,text,CF1650G,转移,dp,dis
From: https://www.cnblogs.com/charphi/p/16888352.html

相关文章

  • Codeforces 833 题解
    A\(n\)是奇数时恰好可以用完,\(n\)是偶数时多出来的一块没用,所以直接输出\((n+1)/2\)即可。B每个字符出现次数都小于等于字符总数,令\(\Sigma\)是字符集大小,那显然......
  • CF1292E Rin and The Unknown Flower 题解
    IO交互题fflush(stdout)害人不浅!!1注意到如果我们发起询问C和O就可以花费2的代价知道整个串,不过代价过高,所以我们考虑减小点代价。考虑发起询问CO,CH,CC,这样我......
  • TheNameCalculator题解
    TheNameCalculator题解题目链接:TheNameCalculator题解首先看程序开启的保护,有Canary和NX栈不可执行。IDA打开程序,shift+F12查看字符串,发现有"Hereisyourflag:"。点......
  • 833——B题题解
    题目链接题目大意:给一串字符串(只包含0~9),定义一个最优子串的定义:如果该子串同字符种类数大于最最多字符出现数,那么这个子串可以被称为最优子串。解题思路:大眼一看这个数......
  • [ARC086F] Shift and Decrement 题解
    linkSolution一个简易的贪心想法是我们肯定是对于一个相同的序列求出操作到它的最小操作次数,看能否\(\leK\)。注意到我们在第\(x\)次A操作后进行\(-1\)操作相当于......
  • 833(DIV2)——C题题解
    题目链接题目大意:给定n个数,你可以对数值为0的数改变其为任意值,问最后前缀和为0的个数的最大值。思路:这题比较可惜,自己的思路没有问题,但是他少了一些东西。对数组进行前......
  • LG_P4588 [TJOI2018] 数学计算 题解
    LuoguP4588题解这个玩意还是挺好想到的,也不难看出他是一个线段树。没想到可以评上蓝。考虑每次操作对于答案的贡献。由于\(x=1\),所以我们相当于是在维护一堆数的积,初始......
  • DTOJ 3498 无限剑制 题解
    题面题目链接题解想了好久,其实很水tt想写题解主要是因为这题题面是Fate很有意思我们注意到“所有\(v_i\)值域在\([1,5]\)”这个部分分,这种情况下,初始的不同情......
  • DTOJ 5932 Counting 题解
    题目链接portal题解认识到了生成函数很好用,于是摆了一篇题解10分直接dp,\(f_{i,j}\)表示走了\(i\)步之后,当前位置在\(j\)的方案数然后就有状态转移方程\(f_{i,......
  • DTOJ 5769 下棋 题解
    题目链接portal题解首先比较容易想到\(dp\),因为任意一段绝对值不超过\(k\),所以白棋个数减黑棋个数要在\([-k,k]\)区间里,我们于是考虑把状态设为白棋减黑棋个数......