EOJ 2月月赛补题
文章目錄
- EOJ 2月月賽補題
- A-昔我往矣
- B-楊柳依依
- C-今我來思(sì)
- D- 雨雪霏霏
EOJ 2月月賽補題
總之就是非常爆炸
A-昔我往矣
題意: 現在有一棵樹,每條邊有邊權,并且給你 555 個點 a,b,c,d,ea,b,c,d,ea,b,c,d,e 。如果從任一點出發能到達其它所有的點,求最小的邊權和。
思路: 很明顯,是個 LCALCALCA 。但是當時不會做。看了題解后才明白。五個點的公共祖先就是 dfsdfsdfs 序最大和最小的兩個點的的 LCALCALCA。至于為什么呢?,按我的理解就是:在用 dfs+RMQdfs+RMQdfs+RMQ 求 LCALCALCA 時,求得是這兩個點的dfs序之間深度最小的節點,而這里多個點時,就是找這五個點形成的最大的 dfsdfsdfs 區間中深度最小的點。
但是我寫的時候是用倍增寫的。順帶發現我那個常數優化過的倍增求LCA的板子好像裂了…這兩天就去補 。
先將 dfsdfsdfs 序最大和最小的點的最短距離加起來,然后對剩下的點,分別與已經加上的點求 LCALCALCA ,找到深度最深的點,就能得到多出來的那一部分鏈的權值。直到全部加完。
代碼:
#include<iostream> #include<cstdio> #include<vector> #include<queue> #include<stack> #include<cmath> #include<map> #include<cstring> #include<string> #include<algorithm> #define fi first #define se second //#include<stdlib.h> //#include <time.h> //srand((unsigned)time(NULL)); using namespace std; typedef long long ll; typedef unsigned long long ull; const int INF = 0x3f3f3f3f; using namespace std; const int N = 1e6 + 10; int n, m, root; struct edge{int u, v;int cost;edge() {}edge(int _u, int _v, int _c) {u = _u; v = _v;cost = _c;} }; vector<edge> G[N]; int fa[N][32], dep[N], lg[N]; //這里的lg是二進制位數 int L[N], R[N], dfsn[N]; //dfs序 int dis[N]; //節點i到根節點的距離 int dfs_cnt = 0; void add_edge(int u, int v, int c) {G[u].push_back(edge(u, v, c));G[v].push_back(edge(v, u, c)); } //void init(int _n) { // dep[0] = 0; dis[0] = 0; // dfs_cnt = 0; // for (int i = 0; i <= _n; i++) // G[i].clear(); // // lg[0] = 0; // for (int i = 1; i <= 20; i++) // lg[i] = lg[i - 1] + (1 << lg[i - 1] == i); //} void dfs(int u, int last) {fa[u][0] = last;dep[u] = dep[last] + 1; //計算深度L[u] = ++dfs_cnt; //dfs序dfsn[dfs_cnt] = u;for (int i = 1; i < 32; i++)fa[u][i] = fa[fa[u][i - 1]][i - 1];for (int i = 0; i < (int)G[u].size(); i++) {int v = G[u][i].v;if (v == last) continue;dis[v] = dis[u] + G[u][i].cost;dfs(v, u);}R[u] = dfs_cnt; } int lca(int x, int y) {if (dep[x] < dep[y]) swap(x, y); // while (dep[x] > dep[y]) // x = fa[x][lg[dep[x] - dep[y]] - 1];for (int i = 31; i >= 0; i--)if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];if (x == y) return x;for (int i = 31; i >= 0; i--)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];return fa[x][0]; } bool cmp(int x, int y) {return L[x] < L[y]; } int main() {scanf("%d", &n);for (int i = 1; i < n; i++) {int u, v, c;scanf("%d%d%d", &u, &v, &c);u++, v++;add_edge(u, v, c);}dfs(1, 0);int q;scanf("%d", &q);while (q--) {vector<int> p;for (int i = 0; i < 5; i++) {int x; scanf("%d", &x); x++;p.push_back(x);}sort(p.begin(), p.end(), cmp); // for (int i = 0; i < 5; i++) // cout << dis[p[i]] << " "; // cout << "\n";int xp = lca(p[0], p[4]); //5個點的lcaint ans = dis[p[0]] + dis[p[4]] - 2 * dis[xp];vector<int> now;now.push_back(p[0]);now.push_back(p[4]);for (int i = 1; i < 4; i++) {int dot = 0;for (int j = 0; j < (int)now.size(); j++) {int nx = lca(p[i], now[j]);if (dot == 0 || dep[nx] > dep[dot])dot = nx;}ans += dis[p[i]] - dis[dot];now.push_back(p[i]);}printf("%d\n", ans);}return 0; }/* 6 4 0 4 0 1 2 1 3 9 3 5 1 3 2 5 */B-楊柳依依
題意: 已知一個圖,圖上有 kkk 個人,每個人從點 aia_iai? 到點 bib_ibi? 。并且每次必定是走最短路徑,若有多條最短路徑,等概率選擇一條。定義 Ei(w)E_i(w)Ei?(w) 表示第 iii 個人經過 www 的概率,現在要求出 ∑i=0k?1E(w)\sum_{i=0}^{k-1}E(w)∑i=0k?1?E(w) 最大的點 www。
思路: 對于每一對起點和終點都 bfsbfsbfs 一次,求出最短路長度以及個數。然后對于圖上每一個點 ppp ,判斷起點到點 ppp 的距離加上 終點到點 ppp 的概率是否與起點到終點距離相等,若相等,就說明這個點在最短路徑上,然后這個點的總概率加上 起點到點p的最短路徑數×終點到點p的最短路徑數起點到終點的最短路徑數\frac{\text{起點到點p的最短路徑數} \times \text{終點到點p的最短路徑數}}{\text{起點到終點的最短路徑數}}起點到終點的最短路徑數起點到點p的最短路徑數×終點到點p的最短路徑數?
最后遍歷一遍所有點,找到最大的點就是答案。
代碼:
#include<iostream> #include<cstdio> #include<vector> #include<queue> #include<stack> #include<cmath> #include<map> #include<cstring> #include<string> #include<algorithm> #define fi first #define se second //#include<stdlib.h> //#include <time.h> //srand((unsigned)time(NULL)); using namespace std; typedef long long ll; typedef unsigned long long ull; const int INF = 0x3f3f3f3f; using namespace std; const int N = 2e5 + 10; const int M = 2e5 + 10; typedef pair<int, int> pii; int cnt[2][N], d[2][N]; int n, m; vector<int> G[N]; void add_edge(int u, int v) {G[u].push_back(v);G[v].push_back(u); } void bfs(int s, int t, int cnt[], int d[]) {for (int i = 0; i <= n; i++) {cnt[i] = 0;d[i] = INF;}d[s] = 0; cnt[s] = 1;queue<pii> q;q.push({0, s});while (q.size()) {pii now = q.front(); q.pop();int dd = now.first, u = now.second;if (dd > d[u]) continue;for (int v : G[u]) {if (d[v] > d[u] + 1) { //更新最短路d[v] = d[u] + 1;cnt[v] = cnt[u];q.push({d[v], v});}else if (d[v] == d[u] + 1) {cnt[v] += cnt[u];}}} } double E[N]; int main() {scanf("%d%d", &n, &m);for (int i = 1; i <= m; i++) {int u, v;scanf("%d%d", &u, &v);add_edge(u, v);}int k;scanf("%d", &k);while (k--) {int a, b;scanf("%d%d", &a, &b);bfs(a, b, cnt[0], d[0]); //起點到終點bfs(b, a, cnt[1], d[1]); //終點到起點if (d[0][b] == INF) continue;for (int i = 0; i < n; i++) {if (d[0][i] + d[1][i] == d[0][b])E[i] += 1.0 * cnt[0][i] * cnt[1][i] / cnt[0][b];}}double mmax = -INF;int pos = -1;for (int i = 0; i < n; i++) {if (E[i] > mmax) {mmax = E[i];pos = i;}}printf("%d\n", pos);return 0; }C-今我來思(sì)
題意: 原本有一個 000 到 n?1n-1n?1 的全排列,但是現在你不知道,你可以多次詢問一段區間的最小值,判斷是否有符合這樣條件的排列,若有多種,只需要輸出一種即可。
思路:
一開始我想用線段樹來得到所有位置的下界,然后先按下界從大到小排序,將每一段下界相同的位置選一個置為該下界,然后從大到小將剩下的數填入空處。最后判斷一下每一個數是否符合下界。然鵝,這個思路只過了兩個點,痛苦面具 應該是后面的構造有問題。
現在來講一個正確的解法:先將詢問中所有對某個 xxx 的限制區間進行分類,然后從大到小,若該數 xxx 無區間限制,就將它 先存起來,繼續下一個數;否則,將該數 xxx 的所有區間的交集求出來(用了一下差分),在該區間找一個未確定的位置,賦為當前的數,然后區間中剩下的位置因為最小是當前的數 xxx ,一次必須要用比 xxx 大的數來賦值,因此要用到之前沒有區間限制的數來賦值。若無法將交集區間全部填滿,就說明不存在。
#include<iostream> #include<cstdio> #include<vector> #include<queue> #include<stack> #include<cmath> #include<map> #include<cstring> #include<string> #include<algorithm> #include<iomanip> #define fi first #define se second //#include<stdlib.h> //#include <time.h> //srand((unsigned)time(NULL)); using namespace std; typedef long long ll; typedef unsigned long long ull; const int INF = 0x3f3f3f3f; using namespace std; const double eps = 1e-7; const int N = 2e5 + 10;struct node{int l, r;node() {}node(int _l, int _r) {l = _l; r = _r;} }a[N]; int have[N]; int top = 0; int f[N]; vector<node> vc[N]; int main() {int n, q;scanf("%d%d", &n, &q);vector<int> ans(n, -1);vector<int> pos(n, -1);for (int i = 0; i < q; i++) {int l, r, x;scanf("%d%d%d", &l, &r, &x);vc[x].push_back(node(l, r));}bool ff = true;for (int i = n - 1; i >= 0 && ff; i--) {if (vc[i].size() == 0) { //沒有對i的限制have[++top] = i;continue;} // for (int j = top; j > 0; j--) // printf("%d ", have[j]); // printf("\n");//用差分尋找交集int L = INF, R = -1;for (int j = 0; j < (int)vc[i].size(); j++) {node now = vc[i][j];L = min(L, now.l);R = max(R, now.r);f[now.l]++; f[now.r + 1]--; // cout << now.l << " " << now.r << "\n";}for (int j = L + 1; j <= R + 1; j++)f[j] += f[j - 1]; // for (int j = 0; j < n; j++) // printf("%d ", f[j]); // printf("\n");int cnt = vc[i].size(); //對i限制的區間個數為cntfor (int j = L; j <= R; j++) {if (f[j] == cnt && ans[j] == -1) {ans[j] = i;pos[i] = j;break;}}//如果沒有交集或者交集的位置都已經被更大的數放滿了if (pos[i] == -1) {ff = false;break;}for (int j = L; j <= R && ff; j++) { //這個區間的最小值是i,必須拿>=i的數填滿if (f[j] = cnt && ans[j] == -1) {if (top == 0) ff = false;else { //因為i是從n-1開始向下遍歷的,所以數組內的數字一定比i大ans[j] = have[top];pos[have[top]] = j;top--;}}}//清空差分數組for (int j = L; j <= R + 1; j++)f[j] = 0;} // for (int i = 1; i <= top; i++) // printf("%d ", have[i]); // printf("\n");for (int i = 0; i < n && ff; i++) {if (ans[i] == -1) {if (top == 0) ff = false;else {ans[i] = have[top];pos[ans[i]] = i;top--;}}}if (ff) {for (int i = 0; i < n; i++) {printf("%d%c", ans[i], i == n - 1 ? '\n' : ' ');}}else {for (int i = 0; i < n; i++) {printf("%d%c", -1, i == n - 1 ? '\n' : ' ');}}return 0; }D- 雨雪霏霏
待補
總結
以上是生活随笔為你收集整理的EOJ 2月月赛补题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java byte[] 字节数组 转 二
- 下一篇: 数组去掉重复元素