写在前面
趁(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 nleq 10^6 , 1leq m leq 2 imes 10^6 , 1leq k leq 10^5 , sum t_i leq 2 imes 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
" , 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
" , sum) ;
for(int j = 1 ; j <= sum ; j++)
{
int v = read() ;
// printf("%lld %lld
" , 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)
【输入输出】:
如题面所说
【数据范围】:
(1leq n leq 10^6 , 1 leq m leq 2 imes 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
") ;//该局面如果是先手必胜态
else printf("Last
") ;//该局面是先手必败态
}
return 0 ;
}