图论小测

写在前面

\(caq,szt\)不在,速来\(rank1\),图论那一部分倒是都没学完,反倒是没考,我就\(Tarjan\)写的博客好 ,(出了Tarjan就睡觉吧),要是早看一下\(szt\)博客,就\(260\)了,真的是,害

期望得分 : 200 ,实际得分160

\(T1\ \ transprt\)

【题目描述】:

给定\(n\)个点,\(m\)条双向边,\(k\)条行驶路线,边有边权\(w\),行驶路线有特定费用\(m\),给定点\(s\),输出\(s\)到其他点的最短路径长度,数据保证图是连通的。

【输入格式】:

输入的第\(1\)行:\(n,m,k,s\),见描述
接下来有\(m\)行: 每行\(3\)个整数,\(x_i,y_i,w_i\),表示第\(i\)条道路连接\(x_i\)\(y_i\)路口,权值为\(w_i\)
接下来有\(k\)行:每行首先\(2\)个整数,\(b_i,t_i\),表示\(i\)路公交车费用为\(b_i\),,并且停靠\(t_i\)个站点,之后输入\(t_i\)个站点

【输出格式】

\(n\)个整数,表示\(s\)点到各点的最短路

【样例】:

【输入】:
5 4 1 1
1 2 1
2 3 1
3 4 1
4 5 1
2 4 2 3 4 5
【输出】:
0 1 2 3 3

【数据范围】:

\(1 \leq n\leq 10^6 , \ \ 1\leq m \leq 2 \times 10^6 , \ \ 1\leq k \leq 10^5 , \ \ \sum t_i \leq 2 \times 10^6 , \ \ 1 \leq k_i , b_i \leq 10^9\)

\(solution\)

首先,如果有点脑子的话,当\(k=0\)时,相当于就跑一个最短路,那么你就可以得到\(30opts\)
其次,我们有一个\(({\sum t_i} ^2)\)的暴力,然后就可以得到\(60opts\),做法就是跑一个最短路,在最短路的时候,首先进行一下判断是否需要换车,然后正常跑一个最短路就行了,

最后,我们发现数据过分地大了,显然\(({\sum t_i} ^2)\)的暴力是过不了,当然这个\(({\sum t_i} ^2)\)只是建的边数,考虑缩小建的边数,做法:对公交车建一个点,路线上的每一个点连向这个新建的点,边权为\(b_i\),新建的点也连向路线上的每一个点,边权为\(0\),然后图的边数为\(n+\sum t_i\),可过

数据点下载

\(Code\)】:

考场\(60\)代码:

/*
 by : Zmonarch
 分类拿分, 
 每一条路线只一次更优,外层枚举路线,内层搞一下 
 在DIJ的板子上加了一个更新路线的操作, 
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
#define int long long
using namespace std ;
const int kmaxn = 2e6 + 10 ;
inline int read()
{
	int x = 0 , f = 1 ; char ch = getchar() ;
	while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
	while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
	return x * f ;
}
int n , m , k , s ;
struct node 
{
	int nxt , u , v , val ;
}e[kmaxn << 1] ;
int tot , h[kmaxn << 1] ;
vector<int> stop[kmaxn] , point[kmaxn]; //公交车在哪一个站点可以上车
int cost[kmaxn] ; //哪一路公交车上车的费用 
bool used[kmaxn] ;  //哪一路公交车是否使用过了 
void add(int u , int v , int val)
{
	e[++tot].nxt = h[u] ;
	e[tot].u = u ;  
	e[tot].v = v ;
	e[tot].val = val ;
	h[u] = tot ;
} 
priority_queue<pair<int , int > > q ;
int dis[kmaxn << 1] ;
bool vis[kmaxn << 1] ;
void dij0(int s)//没有负边,就跑dij ,30分应该是稳了 
{
	memset(dis , 63 , sizeof(dis)) ;
	memset(vis , 0 , sizeof(vis)) ;
	q.push(make_pair(0 , s)) ;
	dis[s] = 0 ; 
	while(!q.empty())
	{
		int u = q.top().second ; 
		q.pop() ;
		if(vis[u]) continue ;
		vis[u] = 1 ;
		for(int i = h[u] ; i ; i = e[i].nxt)
		{
			int  v = e[i].v ;
			if(dis[v] > dis[u] + e[i].val)
			{
				dis[v] = dis[u] + e[i].val ;
				q.push(make_pair(-dis[v] ,v)) ; 
			}
		}
	}
}
void dij1(int s)
{
	memset(dis , 63 , sizeof(dis)) ; 
	memset(vis , 0 , sizeof(vis)) ;
	dis[s] = 0 ; 
	q.push(make_pair(-dis[s] , s)) ;
	while(!q.empty())
	{
	//	printf("test : -------------- u , dis %lld , %lld\n" , u , dis[u]) ;
		int u = q.top().second ;
		q.pop() ;
		if(vis[u]) continue ;
		vis[u] = 1 ; 
		
		for(int i = 0 ; i < point[u].size() ; i++) //枚举出每一个条经过该点公交路线
		{
			int road = point[u][i] ; 
			used[road] = true ; //这条路线已经走过了,如果还要走这条路线回到一个点,还不如直接从这个直接做到那
			for(int j = 0 ; j < stop[road].size() ; j++) //更新一下他下一次能走的点
			{
				int v = stop[road][j] ; // 该路线的下一个节点 
				if(dis[v] > dis[u] + cost[road]) // 更新路线 
				{
					dis[v] = dis[u] + cost[road] ;
					if(!vis[v]) 
					{
						q.push(make_pair(-dis[v] , v) ) ;
					}
				}
			} 
		}
		//路线走完了,就走正常的路了 
		for(int i = h[u] ; i ; i = e[i].nxt) 
		{
			int v = e[i].v ; 
			if(dis[v] > dis[u] + e[i].val) 
			{
				dis[v] = dis[u] + e[i].val ; 
				if(!vis[v]) 
				{
				q.push(make_pair(-dis[v] , v)) ;		
				} 
			}
		}	
	}
}
signed main()
{
	freopen("transprt.in" , "r" , stdin) ; 
	freopen("transprt.out" , "w" , stdout) ;
	n = read() , m = read() , k = read() , s = read() ; 
	for(int i = 1 ; i <= m ; i++)
	{
		int u = read() , v = read() , w = read() ;
		add(u , v , w) ; 
		add(v , u , w) ; 
	}
	if(k == 0) //城市中没有其他的路线,就是每一个到每一个点的最短路
	{
		dij0(s) ;
		for(int i = 1 ; i <= n ; i++)
		{
			printf("%lld " , dis[i]) ;
		}
		return 0 ; 
	} 
	if(k != 0 )   
	{
		for(int i = 1 ; i <= k ; i++)
		{
			cost[i] = read() ;
			int sum = read() ; 
			//printf("test : sum----%lld\n" , sum) ;
			for(int j = 1 ; j <= sum ; j++)
			{
				int v = read() ; 
			//	printf("%lld %lld\n" , v , j ) ;
				stop[i].push_back(v) ; //存下站点 
				point[v].push_back(i) ; //存下每一个节点会有多少条路线经过 
			}
		}
		dij1(s) ;
		for(int i = 1 ; i <= n ;i++) 
		{
			printf("%lld " , dis[i]) ;
		}
	}
	return 0 ;
}

满分代码:


\(T2 \ \ irrigate\)

\(loj\)新的开始同题,建个超级源点一眼秒

/* 
 by : Zmonarch
 知识点 : 最小生成树 
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std ;
const int kmaxn = 2e6 + 10 ; 
inline int read()
{
	int x = 0 , f = 1 ; char ch = getchar() ;
	while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
	while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
	return x * f ;
}
struct node 
{
	int nxt , v , u , val ;
}e[kmaxn << 1] ; 
int fa[kmaxn << 1] ; 
int ans = 0 , n , m ;
int a[kmaxn << 1] ;  
int tot = 0 , cnt = 1 ; 
void add(int u , int v , int w)
{
	e[++tot].u = u ; 
	e[tot].v = v ; 
	e[tot].val = w ; 
}
bool cmp(node x , node y) 
{
	return x.val < y.val ;
} 
int findx(int x) 
{
	if(x == fa[x]) return x ; 
	else return fa[x] = findx(fa[x]) ; 
}
void kruscal()
{
	sort(e + 1 , e + tot + 1 , cmp) ;
	for(int i = 1 ; i <= n ; i++)
	{
		fa[i] = i ; 
	}
	for(int i = 1 ; i <= tot ; i++)
	{
		int fu = findx(e[i].u ) ; 
		int fv = findx(e[i].v ) ; 
		if(fu != fv) 
		{
			fa[fu] = e[i].v ; 
			cnt ++ ; 
			ans += e[i].val ; 
			if(cnt == n + 1 ) return ; //这里加上超级源点是五个点
		}
	}
}
signed main()
{
	freopen("irrigate.in" , "r" , stdin) ; 
	freopen("irrigate.out" , "w" , stdout) ; 
	n = read() ; 
	for(int i = 1 ; i <= n ; i++) 
	{
		a[i] = read() ;
	}
	for(int i = 1 ; i <= n ; i++)
	{
		for(int j = 1 ; j <= n ; j++)
		{
			int w = read() ; 
			if(i == j) continue ;//屏蔽掉自环 
			add(i , j , w) ;
		}
	}
	for(int i = 1 ; i <= n ; i++)  //建立一个超级源点就可以解决掉在不在一个点取水了 
	{
		add(0 , i , a[i]) ;  
	}
	kruscal() ; 
	printf("%lld" , ans) ; 
	return 0 ;
	
}

\(T3 \ \ duel\)

【题目描述】:

给定\(n\)个局面,\(m\)个转移,求解\(n\)个状态是先手必胜状态还是先手必败状态,先手必胜输出\(First\),先手必败输出\(Last\)

【输入输出】:

如题面所说

【数据范围】:

\(1\leq n \leq 10^6 , 1 \leq m \leq 2\times 10^6\)

\(Solution\)

显然,博弈论加拓扑,显然,局面的转移是一个\(DAG\),然后有几个显然的结论
\(1.\)先手获胜局面为先手必胜态
\(2.\)先手落败局面为先手必败态
\(3.\)当前有一个局面为先手必败态的后继局面,那么当前局面必然为先手必胜态的前继局面,为先手必胜态
\(4.\)当前有一个局面为先手必胜态的后继局面,那么当前局面必然为先手必败态额前继局面,应也为先手必败态

同样的,我们发现,入度为\(0\),也就是没有任何给予这一个局面转移,\(so\),该局面为初始局面
出度为\(0\),也就是不会给予任何局面的转移,\(so\)该局面为结束状态

同样的,既然是\(DAG\),那就拓扑序转移即可

参考\(szt\)大佬

\(std : : Code\)

/*
 by : Zmonarch 
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <stdlib.h>
#include <time.h>
#define int long long
const int kmaxn = 1e6 ;
using namespace std ;
inline int read()
{
	int x = 0 , f = 1 ; char ch = getchar() ;
	while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
	while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
	return x * f ;
}
int n , m ;
struct node 
{
	int nxt , v , u , val ;
}e[kmaxn << 1] ; 
int inv[kmaxn] , ino[kmaxn] ; 
bool ans[kmaxn] ;
int tot , h[kmaxn << 1] ;
void add(int u , int v )
{
	e[++tot].nxt = h[u] ;
	e[tot].u = u ;
	e[tot].v = v ;
	h[u] = tot ;
} 
int ans1[kmaxn << 1] ; 
int ans2[kmaxn << 1] ;
queue<int> q ;
void topo()
{
	for(int i = 1 ; i <= n ; i++)
	{
		if(inv[i] == 0) 
		{
			int x = read() ;
			q.push(i) ;
			if(x) ans1[i] = 1 , ans2[i] = 0 ; //先手必赢态
			else ans1[i] = 0 , ans2[i] = 1 ;  
		}
		else ans1[i] = 0 , ans2[i] = 2147483647 ; 
	}
	while(!q.empty())
	{
		int u = q.front() ; 
		q.pop() ;
		for(int i = h[u] ; i ; i = e[i].nxt) 
		{
			int v = e[i].v ; 
			ans1[v] = max(ans1[v] , ans2[u]) ;
			ans2[v] = min(ans2[v] , ans1[u]) ; 
			inv[v] -- ;
			if(inv[v] == 0) q.push(v) ;
		}	
	}
}
signed main()
{
	//freopen("duel.in" ,"r" , stdin) ; 
	//freopen("duel.out","w",stdout) ;
	 n = read() , m = read() ; 
	for(int i = 1 ; i <= m ; i++)
	{
		int u = read() , v = read() ; 
		add(v , u) ;
		inv[u] ++ ;
	}
	topo() ; 
	for(int i = 1 ; i <= n ; i++) 
	{
		if(ans1[i]) printf("First\n") ;//该局面如果是先手必胜态 
		else printf("Last\n") ;//该局面是先手必败态 
	} 
	return 0 ;
}

posted @ 2021-02-02 21:02  SkyFairy  阅读(99)  评论(8编辑  收藏  举报