Codeforces Round #656 (Div. 3)
待補c++
- G
1. 題目分析
- A: 思惟
- B: 思惟
- C: 思惟
- D: 思惟+dfs
- E: 拓撲排序性質
- F: 模擬
2. 題解
A. Three Pairwise Maximums
題意: 給定t個測試樣例,每一個測試樣例給定x,y,z,須要找到a,b,c三個數,使得max(a, b)=x, max(a, c)=y, max(b, c)=z,輸出a,b,c(順序無所謂),若是找不到輸出NO。t~2e4, a、b、c~1e9
題解: max(a, b) = x, max(a, c) = y, 則max(a, b, c) = max(x, y),同理max(a, b, c) = max(x, y) = max(y, z) = max(x, z),則①x=y=z,②x < y = z。所以,只須要把x, y, z排序,最小的爲x,中間的爲y,最大的爲z,判斷y是否爲z,不相等爲NO,相等的話,輸出x, x, y
代碼:
數組
#include <bits/stdc++.h> using namespace std; int main() { int a[3]; int T ; cin >> T; while (T--) { for (int i = 0; i < 3; ++i) scanf("%d", &a[i]); sort(a, a + 3); if (a[1] != a[2]) { cout << "NO\n"; continue; } cout << "YES\n"; cout << a[0] << " " << a[0] << " " << a[1] << endl; } return 0; }
B. Restore the Permutation by Merger
題意: t個測試樣例,每一個測試樣例輸入長度爲2n的數組,該數組是由兩個不改變相對位置的全排列數組merge而成的,每次要求輸出原來的全排列數列。t ~ 400,n ~ 50
題解: 觀察能夠知道,一樣的數字只須要取前一個出現的那個便可,所以,只須要掃一遍,若是這個數字沒出現過,那麼打印;若是沒出現過,那麼跳過
代碼
測試
#include <bits/stdc++.h> using namespace std; int main() { int T; cin >> T; while (T--) { int n; cin >> n; unordered_map<int, int> mp; for (int i = 1, x; i <= n * 2; ++i) { cin >> x; if (mp.count(x)) continue; cout << x << " " ; mp[x] = 1; } cout << endl; } return 0; }
C. Make It Good
題意: 定義一個good數列:每次從該數列的頭或者尾拿出一個數字,拿出來的數字是單調不減的。如今給定t個測試樣例,每一個測試樣例給定一個長度爲n的數列,問須要將長度爲n的數列的刪除前k個數字,使之變爲good數列,這個k最小取多少?\(\sum_{} n~2e5\)
題解: 對於任意兩個數字x, y,若是這兩個數字都是從前面拿出,那麼須要x <= y;對於任意兩個數字x和y,若是這兩個數字都是從後面被拿出,那麼須要x >= y,所以只須要倒着往前掃,找到相似小山坡的形狀,即先增長後降低,一旦再次上升,即爲到達邊界。
代碼:
spa
#include <bits/stdc++.h> using namespace std; int const N = 2e5 + 10; int a[N]; int main() { int T; cin >> T; while (T--) { int n; cin >> n; for (int i = 1; i <= n ; ++i) { scanf("%d", &a[i]); } if (n == 1) { cout << 0 << endl; continue; } int i = n - 1; while (a[i] >= a[i + 1] && i >= 1) i--; while (a[i] <= a[i + 1] && i >= 1) i--; cout << i << endl; } return 0; }
D. a-Good String
題意: 定義一個'a'-good字符串爲:code
- 字符串長度爲1,且只有一個字符'a'
- 字符串長度大於1,前半部分全爲'a',後半部分爲'b'-good字符串
- 字符串長度大於1,後半部分全爲'a,',前半部分爲'b'-good字符串
如今有t個測試樣例,每一個測試樣例給定一個長度爲n的字符串,每次操做能夠把任意位置的一個字符替換爲任意一個小寫字母,問最少須要多少次這樣的操做後,才能使得當前字符串爲'a'-good字符串?保證全部的字符串總長度爲2e5
題解: 仔細分析可知,'a'-good字符串是遞歸定義的,確定爲一半爲a,另外一半的前一半爲b。另外一半的後一半的前一半爲c,。。。,即出現16-8-4-2-1這樣的狀況,所以每次只須要把當前字符串分爲兩半,分別統計兩半內當前字符哪一個出現的多,少的那個則爲b字符串處理,不斷dfs便可,因爲這個規模不大,所以不會超時
代碼:
排序
#include <bits/stdc++.h> using namespace std; int T, n; int const N = 2e5 + 10; char str[N]; int dfs(int st, int ed, char a) { if (st == ed) { if (str[st] != a) return 1; else return 0; } int cnt1 = 0, cnt2 = 0; for (int i = st; i <= (st + ed) / 2; ++i) if (str[i] != a) cnt1++; for (int i = (st + ed) / 2 + 1; i <= ed; ++i) if (str[i] != a) cnt2++; return min(cnt1 + dfs((st + ed) / 2 + 1, ed, a + 1), cnt2 + dfs(st, (st + ed) / 2, a + 1)); } int main() { // freopen("in.txt", "r", stdin); // freopen("out.txt", "w", stdout); cin >> T; while (T--) { cin >> n; scanf("%s", str); cout << dfs(0, n - 1, 'a') << endl; } return 0; }
E. Directing Edges
題意: 給定t個測試樣例,每一個測試樣例給定n和m,n爲圖的點數,m爲圖的邊數,給定m條邊,每條邊爲op, a, b。若是op爲1,表示有一條有向邊a -> b;若是op爲0,表示a和b之間有一條無向邊。如今要求把無向邊變成有向邊(把a--b變成a->b或b->a),使得最後這m條邊沒有環。【累加】n、m~2e5
題解: 題意能夠轉換爲如今有一張有向圖,要求向這張有向圖內加入有向邊,使得這張有向圖沒有環,求加邊的方法。所以,能夠先作一次拓撲排序,求出拓撲序,若是沒有拓撲序,說明已經存在環;不然,只須要加入拓撲序小的指向拓撲序大的邊便可。(由於想要造成環,必須有迴路,則必須a->b,同時b->a,一旦出現拓撲序,說明存在a->b,不存在b->a,所以只要不加入b->a,則不可能出現環)
代碼以下:
遞歸
#include<bits/stdc++.h> using namespace std; int const N = 2e5 + 10; int t, n, m; set<int> point; int e[N * 2], ne[N * 2], idx, h[N]; int d[N], sorted[N]; vector<pair<int, int> > undirect, direct; vector<int> ans; struct Edge{ int op, u, v; }; vector<Edge> E; void top_sort() { queue<int> q; for (int i = 1; i <= n; ++i) { if (!d[i]) { // cout << i << endl; q.push(i); } } while (q.size()) { auto t = q.front(); q.pop(); ans.push_back(t); for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; d[j]--; if (!d[j]) { q.push(j); } } } for (int i = 0; i < ans.size(); ++i) { sorted[ans[i]] = i + 1; } return ; } void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int main() { // freopen("in.txt", "r", stdin); // freopen("out.txt", "w", stdout); cin >> t; while (t--) { memset(h, -1, sizeof h); memset(d, 0, sizeof d); idx = 0; E.clear(); memset(sorted, 0, sizeof sorted); ans.clear(); cin >> n >> m; for (int i = 0, op, a, b; i < m; ++i) { scanf("%d%d%d", &op, &a, &b); E.push_back({op, a, b}); if (op == 1) { add(a, b); d[b]++; } } top_sort(); // cout << ans.size() << endl; if (ans.size() != n) { cout << "NO\n"; continue; } cout << "YES\n"; for (int i = 0; i < E.size(); ++i) { if (E[i].op == 1) { cout << E[i].u << " " << E[i].v << endl; } else { if (sorted[E[i].u] < sorted[E[i].v]) cout << E[i].u << " " << E[i].v << endl; else cout << E[i].v << " " << E[i].u << endl; } } } return 0; }
F. Removing Leaves
題意: t個測試樣例,每一個測試樣例給定一棵有n個節點的無根樹,每次選擇一個有k個葉子的節點,刪除這k個葉子,問最多可以作多少次這樣的操做?第一行輸入t,然後每一個樣例先輸入n和k,再輸入n-1條邊。t ~ 2e4, \(\sum_{}n<=2e5\)
題解: 因爲每次只能刪除某個點的k個葉子,所以使用一個隊列把可以刪除的點給維護起來便可,每次刪除k個葉子節點後,看是否能產生新的葉子,產生新的擁有k個葉子以上的節點,有的話放入隊列。
代碼:
隊列
#include<bits/stdc++.h> using namespace std; const int N=2e5+100; vector<int> g[N]; int leave[N]; int all[N]; int t,n,k; int main () { scanf("%d",&t); while (t--) { scanf("%d%d",&n,&k); for (int i=1;i<=n;i++) g[i].clear(),leave[i]=0,all[i]=0; for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); g[x].push_back(y); g[y].push_back(x); } if (k==1) { printf("%d\n",n-1); continue; } int ans=0; for (int i=1;i<=n;i++) { all[i]=g[i].size(); if (all[i]==1) leave[g[i][0]]++; } int inq[n+1]={0}; queue<int> q; for (int i=1;i<=n;i++) { if (leave[i]>=k) { q.push(i); inq[i]=1; } } while (!q.empty()) { int u=q.front(); q.pop(); ans++; leave[u]-=k; all[u]-=k; inq[u]=0; if (leave[u]>=k) { q.push(u); inq[u]=1; } if (all[u]==1) { all[u]=0; for (auto v:g[u]) { if (all[v]) { leave[v]++; if (leave[v]>=k&&!inq[v]) { q.push(v); inq[v]=1; } } } } } printf("%d\n",ans); } }