图论小测
写在前面
趁\(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 ;
}