20210128图论专场
写在前面
期望得分:\(300pts\),实际得分:\(130pts\)
最后半小时硬肝了两道题,思路看着没问题,本来想像那位61级学长AK掉的,结果直接挂掉了
感觉这套题T2比较傻逼,主要是以前做过两次原题,T1要灵活处理一下,T3是以前没涉及到的博弈论,没想到自己还能推个七七八八
T1的思路后来被yu__xuan轻松Hack,悲!
T3的思路貌似是正确的,只不过在初始化时有点问题,感谢yu__xuan为我调码/cy
T1
简述题意:
有 \(n\) 个结点和 \(m\) 条双向边,边有边权。给定一个 \(S\) ,求 \(S\) 到所有结点的最短路。不同的是,还有 \(k\) 条公交路线,第 \(i\) 条路线有 \(t_i\) 个路口,路线是固定的,可以往返行驶,上第 \(i\) 条公交车需要 \(b_i\) 元的费用,但费用是一次性的。如果再次上车则需要再次付费。
数据范围:\(1 \le n \le 1e5,\ 1 \le m \le 2e5,\ 1 \le k \le 5e4,\ \sum{t_i} \le 2e5,\ 1 \le k_i,b_i \le 1e9\)
Solution:
因为要求到所有点的最短路,直接上dij跑,那么如何处理公交线路?
最先到达的点所对应的公交线路一定是到这条公交线路的最近的点
考虑经过的次数:做一次车就能到达的点肯定不需要做两次,所以每条线路只可能经过一次
那么就可做了,只需要我们在跑最短路的时候顺便用遇到的公交线路更新一下就好了,顺便打个标记,避免用多次浪费时间
Code
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e5+5;
const int MAXM = 2e5+5;
const LL INF = 1e17+7;
const int mod = 1e9+7;
struct edge{
int to; LL w; int nxt;
}e[MAXM << 1];
int head[MAXN], num_edge = 0;
struct node{
int bh; LL val;
bool operator < (const node &b) const { return val > b.val; }
};
int n, m, k, S, c[MAXN];
LL dis[MAXN];
bool vis[MAXN], used[MAXN];
vector<int> p[MAXM], bus[MAXM];
priority_queue<node> q;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
void add_edge(int from, int to, LL w){ e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }
void DJ(){
memset(dis, 0x3f, sizeof dis);
dis[S] = 0;
q.push((node){S, 0});
while(!q.empty()){
node t = q.top(); q.pop();
if(vis[t.bh]) continue;
vis[t.bh] = true;
for(int i = 0; i < p[t.bh].size(); ++i){
int u = p[t.bh][i];
if(!used[u]){
used[u] = true;//每条公交线路只会做一次
for(int j = 0; j < bus[u].size(); ++j){
if(dis[bus[u][j]] > dis[t.bh] + c[u]){
dis[bus[u][j]] = dis[t.bh] + c[u];
if(!vis[bus[u][j]]) q.push((node){bus[u][j], dis[bus[u][j]]});
}
}
}
}
for(int i = head[t.bh]; i; i = e[i].nxt){
int v = e[i].to;
if(dis[v] > dis[t.bh] + e[i].w){
dis[v] = dis[t.bh] + e[i].w;
if(!vis[v]) q.push((node){v, dis[v]});
}
}
}
}
int main()
{
freopen("transprt.in","r",stdin);
freopen("transprt.out","w",stdout);
n = read(), m = read(), k = read(), S = read();
for(int i = 1, u, v, w; i <= m; ++i){
u = read(), v = read(), w = read();
add_edge(u, v, w), add_edge(v, u, w);
}
for(int i = 1, t; i <= k; ++i){
c[i] = read(), t = read();
for(int j = 1; j <= t; ++j){
int x = read();
p[x].push_back(i);//存每个点位于那条公交线路上
bus[i].push_back(x);//存每条公交线路上有那几个点
}
}
DJ();
for(int i = 1; i <= n; ++i) cout<<dis[i]<<" ";
return 0;
}
T2
与loj上的新的开始同题,可以参考这篇文章中的solution,一眼题,就不赘述了
T3
简述题意:
已知这个游戏有 \(n\) 个局面和 \(m\) 条转移 (即 DAG 有 \(n\) 个点和 \(m\) 条边) , 有唯一的
起始局面,但可能有多个终止局面。
给你一个DAG,最后有若干个整数,依次代表所有终止局面的获胜方,按照其节点
编号从小到大给出。如果数字为 \(1\),那么在该终止局面的先手获胜;如果数字为
\(0\),则后手获胜。求出每个局面是先手必胜态还是先手必败态,分别输出 \(Frist\) 和 \(Last\) 。
Solution:
本题涉及到博弈论相关知识,因为开始不是很懂,后来在人工样例解释下才弄明白的。
关于博弈论可以参考这篇文章
因为两个人都很聪明,所以两个人都会想向自己必胜的状态转移
反向建图,并设两个数组 \(ansl,ansr\),分别存先手和后手在结点 \(i\) 是什么状态
初始化根据题意把最终先手和后手的状态存上
注意:如果一个点不是结束状态,应将 \(ansl\) 赋成0, \(ansr\) 赋成极大值,而不都是0
考虑怎么转移状态?有点类似于在拓扑上DP
因为是反向建图,所以枚举到一个状态时,其后面的状态都已经求出,对答案无影响
对于先手来说,他的下一轮是后手,所以肯定会选择后手必胜的状态(当然前提是有,如果没有就直接转移)
对于后手来说,因为它没有选择的权利,所以先手一定会让它转移到下一轮先手必败的状态(前提也是存在这个状态,如果没有就直接转移)
最后输出先手是否必胜就欧克了
下面请食用代码(附注释,应该会比较好懂
Code
/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e5+5;
const int MAXM = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
struct edge{
int to, nxt;
}e[MAXM];
int head[MAXN], num_edge = 0;
int n, m;
int id[MAXN];
int ansl[MAXN], ansr[MAXN];//ansl存的是先手状态,ansr存的是后手状态
//bool ans[MAXN];
queue<int> q;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
void add_edge(int from, int to){ e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }
void topsort(){
for(int i = 1, x; i <= n; ++i) {
if(!id[i]) {//如果发现一个没有入度的点,一定是终点
x = read(), q.push(i);//读一下它的状态
//ans[i] = x;
if(x) ansl[i] = 1, ansr[i] = 0;//存状态
else ansl[i] = 0, ansr[i] = 1;o1
} else ansl[i] = 0, ansr[i] = 2147483647;
}
while(!q.empty()){
int t = q.front(); q.pop();
for(int i = head[t]; i; i = e[i].nxt){
int v = e[i].to;//因为是拓扑排序遍历,在此之前的ans都已经求出
//if(!ans[t]) ans[v] = true;
//if(ans[t] && !ans[v]) ans[v] = false;
ansl[v] = max(ansl[v], ansr[t]);//这一轮的先手在下一轮会成为后手,所以它尽可能的选择下一轮作为后手能赢的情况
ansr[v] = min(ansr[v], ansl[t]);//这一轮的后手在下一轮会成为先手,所以它的对手会尽可能给它留下作为先手能输的情况
id[v]--;
if(!id[v]) q.push(v);
}
}
}
int main()
{
// freopen("duel.in", "r", stdin);
// freopen("duel.out", "w", stdout);
n = read(), m = read();
for(int i = 1, u, v; i <= m; ++i){
u = read(), v = read();
add_edge(v, u);//反向建图
id[u]++;//入度++
}
topsort();//拓扑遍历
for(int i = 1; i <= n; ++i){
if(ansl[i]) printf("First\n");
else printf("Last\n");
}
return 0;
}
注:注释掉的ans数组是题解给出的解法
可以参考一下