ABC166
連続開催ABCの2日目である.しばらくABCはなさそうに見える.
結果
問題 | ACまでに要した時間 | AC | WA | TLE |
---|---|---|---|---|
A | 0:49 | 1 | 0 | 0 |
B | 3:40 | 1 | 0 | 0 |
C | 21:06 | 1 | 0 | 0 |
D | 54:37 | 1 | 0 | 0 |
Rating | 順位 |
---|---|
671 (+34) | 3481 |
解答
C
計算量はでいけそうだなと思ったので,双方向グラフとして道を表現して全探索した.
submitでは vector<bool> visit
で到達したを記録していたけど,よく考えたらNGじゃない(道がつながっているHよりも高い)のみをリストアップすれば答えは求められた.
#include <bits/stdc++.h> #define REP(i, x) REPI(i, 0, x) #define REPI(i, a, b) for (int i = int(a); i < int(b); ++i) #define ALL(x) (x).begin(), (x).end() typedef long long ll; using namespace std; int main() { cin.tie(0); ios::sync_with_stdio(false); int N, M; cin >> N >> M; vector<ll> H(N); REP(i, N) { cin >> H.at(i); } vector<vector<int>> path(N); int a, b; REP(i, M) { cin >> a >> b; --a; --b; path.at(a).push_back(b); path.at(b).push_back(a); } int ans = 0; vector<bool> visit(N, false); REP(i, N) { if (path.at(i).size() == 0 || visit.at(i)) { continue; } visit.at(i) = true; ll h = H.at(i); bool highest = true; for (int const &d : path.at(i)) { if (H.at(d) >= h) { highest = false; break; } } if (highest) { for (int const &d : path.at(i)) { visit.at(d) = true; } ++ans; } } REP(i, N) { if (!visit.at(i)) { ++ans; } } cout << ans << endl; return 0; }
D
の最大値は109なのでビビってしまうが,よく考えれば100を5乗するとその最大値よりも明らかに大きくなる.そのため直感的に探索範囲は大したことがないことが分かる.
また,仮にが109だとすると,がとり得る最大値はたかだかである. よって,なのでの範囲を探索すればいいこと になる.
しかしコンテスト中は,ここまでの考察があまりに短時間でできてしまったために,逆にビビって]の範囲で全探索している.ビビらなければもっと早くACできたはず.
#include <bits/stdc++.h> #define REP(i, x) REPI(i, 0, x) #define REPI(i, a, b) for (int i = int(a); i < int(b); ++i) #define ALL(x) (x).begin(), (x).end() typedef long long ll; using namespace std; int main() { cin.tie(0); ios::sync_with_stdio(false); ll X; cin >> X; vector<ll> A(1100), B(1100); REP(i, 1001) { A.at(i) = B.at(i) = pow(i, 5); } REP(i, 1001) { REP(j, 1001) { if (A.at(i) - B.at(j) == X) { cout << i << " " << j << endl; return 0; } else if ((-1) * A.at(i) - B.at(j) == X) { cout << -i << " " << j << endl; return 0; } else if (A.at(i) + B.at(j) == X) { cout << i << " " << -j << endl; return 0; } } } return 0; }
E(解説AC)
解説読んでAC.
ポイントとしては一致させたい変数を事前計算しておく.
同じに対してかつを満たす組み合わせを数え上げれば解けるが,何も考えずに全探索するととなりTLE.
そこで,以下のように変形した式の両辺をkey,valueにiまたはjを格納したmapにそれぞれ入れておくと,最終的に両者のkeyが一致するiまたはjの組み合わせを数えればよく, で解ける.
このように分離する利点は,それぞれのmapのkeyがとにのみ依存し,両者が独立した集合になる点.つまり,それぞれのmapのkeyのみを比較し,valueに含まれているiまたはjの数を乗じるだけで組み合わせ数が分かるため計算がシンプルになる.
これがもしもとの条件のままとをkeyとすると,valueにはiとjのpairのsetを入れておかねばならず,最後に組み合わせを数え上げる時の判定が面倒くさい.多分これでも解けなくはないが,空間計算量も増加するためメリットは無い.
ここで,はmapへのinsertコストを考慮.最大要素数のvectorを用意してそこに格納していけば でも解ける気がする.
#include <bits/stdc++.h> #define REP(i, x) REPI(i, 0, x) #define REPI(i, a, b) for (int i = int(a); i < int(b); ++i) #define ALL(x) (x).begin(), (x).end() typedef long long ll; using namespace std; int main() { cin.tie(0); ios::sync_with_stdio(false); int N; cin >> N; map<ll, set<ll>> L; map<ll, set<ll>> R; ll a; for (ll i = 1; i <= N; ++i) { cin >> a; ll l = i + a; ll r = i - a; if (L.find(l) == L.end()) { set<ll> s; s.insert(i); L.insert(make_pair(l, s)); } else { L.at(l).insert(i); } if (r > 0) { if (R.find(r) == R.end()) { set<ll> s; s.insert(i); R.insert(make_pair(r, s)); } else { R.at(r).insert(i); } } } ll ans = 0; for (auto const &it : L) { if (R.find(it.first) != R.end()) { ans += it.second.size() * R.at(it.first).size(); } } cout << ans << endl; return 0; }
ダメだった点
Dに時間をかけすぎた.根拠のありそうな解析が済んだらさっさとsubmitした方が結果的に良かっただろうと思う.早い緑コーダーは30分程度でsubmitしていたのでそれぐらいで解けるべきであった.
良かった点
難易度が低めなABCだったと思うがそれでもとりあえずABCDの4完を達成できたのは良かった.Eも解ければもっと良かったが以外の解法が思いつかなかった.