指望得分:\(300pts\),实际得分:\(130pts\)html
最后半小时硬肝了两道题,思路看着没问题,原本想像那位61级学长AK掉的,结果直接挂掉了node
感受这套题T2比较傻逼,主要是之前作过两次原题,T1要灵活处理一下,T3是之前没涉及到的博弈论,没想到本身还能推个七七八八ios
T1的思路后来被yu__xuan轻松Hack,悲!
T3的思路貌似是正确的,只不过在初始化时有点问题,感谢yu__xuan为我调码/cygit
简述题意:数组
有 \(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; }
与loj上的新的开始同题,能够参考这篇文章中的solution,一眼题,就不赘述了
简述题意:
已知这个游戏有 \(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数组是题解给出的解法
能够参考一下