20210128圖論專場

目錄

寫在前面

指望得分:\(300pts\),實際得分:\(130pts\)html

最後半小時硬肝了兩道題,思路看着沒問題,原本想像那位61級學長AK掉的,結果直接掛掉了node

感受這套題T2比較傻逼,主要是之前作過兩次原題,T1要靈活處理一下,T3是之前沒涉及到的博弈論,沒想到本身還能推個七七八八ios

T1的思路後來被yu__xuan輕鬆Hack,悲!
T3的思路貌似是正確的,只不過在初始化時有點問題,感謝yu__xuan爲我調碼/cygit

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\)spa

Solution:code

由於要求到全部點的最短路,直接上dij跑,那麼如何處理公交線路?
最早到達的點所對應的公交線路必定是到這條公交線路的最近的點
考慮通過的次數:作一次車就能到達的點確定不須要作兩次,因此每條線路只可能通過一次htm

那麼就可作了,只須要咱們在跑最短路的時候順便用遇到的公交線路更新一下就行了,順便打個標記,避免用屢次浪費時間blog

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數組是題解給出的解法

能夠參考一下

相關文章
相關標籤/搜索