位. $75 で何買おう.
Breaking the Records
解法
寝ながら書く.
ソース
#include<bits/stdc++.h> using namespace std; typedef long long int64; int main() { int n, cod; cin >> n; cin >> cod; int a = 0, b = 0; int s = cod, t = cod; for(int i = 1; i < n; i++) { int p; cin >> p; if(s < p) { s = p; ++a; } if(t > p) { t = p; ++b; } } cout << a << " " << b << endl; }
Separate the Numbers
解法
の部分文字列が連結される数値の一部なのでそれを総当り.
寝ながら書いたら謎(謎ではない)のWAをしたので起きた. なんか連続だけど, 最後で途中で切れているやつもOKみたいにしてた(伝わって).
ソース
#include<bits/stdc++.h> using namespace std; typedef long long int64; int main() { int q; cin >> q; while(q--) { string S; cin >> S; for(int i = 1; i <= S.size() / 2; i++) { int64 t = stoll(S.substr(0, i)); string q; if(t == 0) break; for(int x = 0; x < 35; x++) { q += to_string(t + x); if(q == S) { cout << "YES" << " " << t << endl; goto foo; } } } cout << "NO" << endl; foo:; } }
Game of Two Stacks
問題概要
スタック と がある.
両方のスタックの上から, 取り除いた要素の合計が を超えない範囲で上から取り除く.
取り除ける要素の個数の最大値を求めよ.
解法
こういうのは一方を固定すると大体上手くいく.
一方のスタックで取り除く範囲は総当りするとして, もう一方は二分探索でえいってできる( 以下の最大の値をもつ要素位置を求める問題になる).
ソース
#include<bits/stdc++.h> using namespace std; typedef long long int64; int main() { int64 g, n, m, x, a[100001] = {}, b[100001] = {}; cin >> g; while(g--) { cin >> n >> m >> x; ++n, ++m; for(int i = 1; i < n; i++) { cin >> a[i]; a[i] += a[i - 1]; } for(int i = 1; i < m; i++) { cin >> b[i]; b[i] += b[i - 1]; } int ret = 0; for(int i = 0; i < n; i++) { if(a[i] > x) break; int qs = upper_bound(b, b + m, x - a[i]) - b - 1; ret = max(ret, i + qs); } cout << ret << endl; } }
The Story of a Tree
問題概要
頂点の木が与えられる.
木の根はランダムに選択される.
また, 個の 「頂点 の親は である」という推測の情報が与えられる. この情報のうち 個以上当たっていれば勝ちである.
このとき勝ちとなる確率を分数で求めよ.
解法
根となる候補は 個なので, 個の頂点すべてを根としたとき勝ちかどうかを試せば良い.
普通にやると なので †全方位木DP†(これすき) をして にする.
ある頂点から別の頂点に移動するとき, 正解の個数が変化するのはそれを結ぶ辺のみなので簡単(?)に求まる.
ソース
#include<bits/stdc++.h> using namespace std; int Q, N, G, K; vector< int > g[100000]; set< pair< int, int > > s; int isok[100000]; void dfs(int idx, int par = -1) { for(auto &to : g[idx]) { if(to == par) continue; dfs(to, idx); isok[idx] += isok[to] + s.count({to, idx}); } } int rec(int idx, int parsum, int par = -1) { parsum += isok[idx]; int ret = parsum >= K; for(auto &to : g[idx]) { if(to == par) continue; ret += rec(to, parsum - isok[to] - s.count({to, idx}) + s.count({idx, to}), idx); } return (ret); } int main() { cin >> Q; while(Q--) { for(int i = 0; i < N; i++) g[i].clear(); s.clear(); memset(isok, 0, sizeof(isok)); cin >> N; for(int i = 0; i < N - 1; i++) { int a, b; cin >> a >> b; --a, --b; g[a].push_back(b); g[b].push_back(a); } cin >> G >> K; while(G--) { int a, b; cin >> a >> b; --a, --b; s.emplace(b, a); } dfs(0); int sum = rec(0, 0); int gcd = __gcd(N, sum); cout << sum / gcd << "/" << N / gcd << endl; } }
Sherlock’s Array Merging Algorithm
問題概要
いくつかの配列 を入力すると, 擬似コード(問題参照) を通して つの配列 が生成される.
今この配列 が分かっている. もとの として考えられる組み合わせの個数を で割った余りで求めよ.
解法
† †(は?.
ぐっと睨んで擬似コードが意味することを整理してみる.
- もとの配列の個数が 個だったとする.
- 全ての配列 から 番目の要素を取り出して, それら 個の値をソートして に追加する.
- 空でない全ての配列 から 番目の要素を取り出して, それら 個( 個より大きいとすると, 回目の操作も より大きくなるので) の値をソートして に追加する.
- これを全ての配列が空になるまで繰り返す.
すると, 取り出す個数は単調減少で, 回で取り出す区間は単調増加ということがわかる.
任意の区間がソートされているか知りたい気持ちになるが, これは愚直にやると でできる.
ここまで分かれば, あとは状態に「取り出す個数」と「配列 上での現在の位置」を持って頑張ってDPすれば求めることができる.
ソース
なんかよくみたら だったんだけど, はやいみたい.
#include<bits/stdc++.h> using namespace std; typedef long long int64; const int mod = 1e9 + 7; inline int64 modPow(int64 x, int64 n) { if(n == 0) return (1); int64 ret = modPow(x, n / 2); (ret *= ret) %= mod; if(n & 1) (ret *= x) %= mod; return (ret); } inline int64 modInv(int64 a) { return (modPow(a, mod - 2)); } int N, M[1202]; bool sorted[1202][1202]; int dp[1201][1201]; int64 fact[1300], rfact[1300]; int rec(int idx, int sz) { if(idx == N) return (1); if(~dp[idx][sz]) return (dp[idx][sz]); int64 ret = 0; for(int j = idx; j < N; j++) { const int nsz = j - idx + 1; if(nsz > sz) break; if(sorted[idx][j]) { int64 qual = fact[sz] * rfact[sz - nsz] % mod; ret += qual * rec(j + 1, nsz) % mod; ret %= mod; } } return (dp[idx][sz] = ret); } int main() { fact[0] = rfact[0] = 1; for(int i = 1; i < 1300; i++) { fact[i] = fact[i - 1] * i % mod; rfact[i] = modInv(fact[i]); } cin >> N; for(int i = 0; i < N; i++) { cin >> M[i]; } for(int i = 0; i < N; i++) { sorted[i][i] = true; for(int j = i + 1; j < N; j++) { if(M[j - 1] > M[j]) break; sorted[i][j] = true; } } memset(dp, -1, sizeof(dp)); int ret = 0; for(int i = 0; i < N; i++) { if(sorted[0][i]) { ret += rec(i + 1, i + 1); ret %= mod; } } cout << ret << endl; }
ここからは全部部分点….
Querying Sums on Strings
問題概要
を, 文字列 の区間 の部分文字列をとってきたとき, これが の中で現れた個数と定義する.
文字列 と 個のペア , を求める 個のクエリ () ( の和は 以下) が与えれられるので全て求めよ.
解法
全ての制約が で怖いが の和は 以下なので, の大小で解法を変えると上手くいく. いく? いかなかった( 点).
のときは で解く. のときは で解けてほしい(log を消さないとダメ?).
どちらも Suffix Array と何かを組み合わせてごにょると解けるのは確か.
SA勉強しないとなぁ.
ソース
#include<bits/stdc++.h> using namespace std; struct SuffixArray { vector< int > SA; string s; void Build_SA(const string &str) { s = str; SA.resize(s.size()); iota(begin(SA), end(SA), 0); sort(begin(SA), end(SA), [&](const int &a, const int &b) { if(s[a] == s[b]) return (a > b); return (s[a] < s[b]); }); vector< int > classes(s.size()), c(s.size()), cnt(s.size()); for(int i = 0; i < s.size(); i++) { c[i] = s[i]; } for(int len = 1; len < s.size(); len <<= 1) { for(int i = 0; i < s.size(); i++) { if(i > 0 && c[SA[i - 1]] == c[SA[i]] && SA[i - 1] + len < s.size() && c[SA[i - 1] + len / 2] == c[SA[i] + len / 2]) { classes[SA[i]] = classes[SA[i - 1]]; } else { classes[SA[i]] = i; } } iota(begin(cnt), end(cnt), 0); copy(begin(SA), end(SA), begin(c)); for(int i = 0; i < s.size(); i++) { int s1 = c[i] - len; if(s1 >= 0) SA[cnt[classes[s1]]++] = s1; } classes.swap(c); } } int operator[](int k) const { return (SA[k]); } int size() const { return (s.size()); } bool lt_substr(char *t, int len, int si = 0, int ti = 0) { int sn = s.size(), tn = len; while(si < sn && ti < tn) { if(s[si] < t[ti]) return (true); if(s[si] > t[ti]) return (false); ++si, ++ti; } return (si >= sn && ti < tn); } int lower_bound(char *t, int len) { int low = -1, high = SA.size(); while(high - low > 1) { int mid = (low + high) >> 1; if(lt_substr(t, len, SA[mid])) low = mid; else high = mid; } return (high); } pair< int, int > lower_upper_bound(char *t, int len) { int idx = lower_bound(t, len); int low = idx - 1, high = SA.size(); t[len - 1]++; while(high - low > 1) { int mid = (low + high) >> 1; if(lt_substr(t, len, SA[mid])) low = mid; else high = mid; } t[len - 1]--; return (make_pair(idx, high)); } }; struct LongestCommonPrefixArray { vector< int > LCP, rank; LongestCommonPrefixArray(SuffixArray &SA) { Build_LCP(SA); } void Build_LCP(SuffixArray &SA) { string &s = SA.s; rank.resize(s.size()); for(int i = 0; i < s.size(); i++) { rank[SA[i]] = i; } LCP.resize(s.size() - 1); for(int i = 0, h = 0; i < s.size(); i++) { if(rank[i] + 1 < s.size()) { for(int j = SA[rank[i] + 1]; max(i, j) + h < s.length() && s[i + h] == s[j + h]; ++h); LCP[rank[i]] = h; if(h > 0) --h; } } } int operator[](int k) const { if(k < 0) return (0); return (LCP[k]); } int size() const { return (LCP.size() + 1); } }; const int INF = 1 << 30; template< class T > struct RMQ { vector< vector< T > > dat; template< class S > void build(S first, S last) { int n = last - first, m = 32 - __builtin_clz(n); dat.resize(m, vector< T >(n)); dat[0].assign(first, last); for(int i = 0; i < m - 1; i++) for(int j = 0; j + (1 << i) < n; j++) dat[i + 1][j] = min(dat[i][j], dat[i][j + (1 << i)]); } T query(int l, int r) { int k = 31 - __builtin_clz(r - l); return min(dat[k][l], dat[k][r - (1 << k)]); } }; struct CumulativeSum { vector< int > data; CumulativeSum(int sz) : data(++sz, 0) {}; inline void add(int k, int x) { data[k] += x; } void build() { for(int i = 1; i < data.size(); i++) { data[i] += data[i - 1]; } } inline int query(int k) { if(k < 0) return (0); return (data[min(k, (int) data.size() - 1)]); } }; int n, m, q, k; string s; int l[100000], r[100000]; string c[100000]; int a[100000], b[100000]; int start_pos[100000]; string all_str; long long ans[100000]; void KQ() { all_str += s; for(int i = 0; i < q; i++) { start_pos[i] = all_str.size(); all_str += c[i]; } SuffixArray sa; sa.Build_SA(all_str); LongestCommonPrefixArray lcp(sa); vector< int > vect(all_str.size()); for(int i = 0; i < all_str.size(); i++) vect[i] = lcp[i - 1]; RMQ< int > tree; tree.build(begin(vect), end(vect)); vector< int > add[100001], del[100001]; long long now_v[100001] = {}; int st[100001] = {}, gt[100001]; fill_n(gt, 100001, all_str.size()); for(int i = 0; i < q; i++) add[a[i]].push_back(i); for(int i = 0; i < q; i++) del[b[i]].push_back(i); vector< vector< int > > corsac(k + 1, vector< int >(all_str.size() + 1, 0)); vector< vector< int > > bits(k + 1, vector< int >(k + 1, 0)); for(int substr_length = 1; substr_length <= k; substr_length++) { for(int pos = 0; pos <= n - substr_length; pos++) { int &low = st[pos]; int high = lcp.rank[pos]; while(high - low > 0) { int mid = (low + high) >> 1; if(tree.query(mid + 1, lcp.rank[pos] + 1) >= substr_length) high = mid; else low = mid + 1; } int low2 = lcp.rank[pos] + 1; int &high2 = gt[pos]; while(high2 - low2 > 0) { int mid = (low2 + high2 + 1) >> 1; if(tree.query(lcp.rank[pos] + 1, mid) >= substr_length) low2 = mid; else high2 = mid - 1; } ++corsac[substr_length][low]; --corsac[substr_length][low2]; } int now = 0; for(int i = 0; i <= all_str.size(); i++) { now += corsac[substr_length][i]; corsac[substr_length][i] = now; } } for(int i = 0; i < m; i++) { for(auto &idx : add[i]) { for(int j = 0; j < k; j++) { for(int z = 1; z <= k; z++) { now_v[idx] -= 1LL * bits[j][z] * corsac[z][lcp.rank[start_pos[idx] + j]]; } } } bits[l[i]][r[i] - l[i] + 1]++; for(auto &idx : del[i]) { for(int j = 0; j < k; j++) { for(int z = 1; z <= k; z++) { now_v[idx] += 1LL * bits[j][z] * corsac[z][lcp.rank[start_pos[idx] + j]]; } } } } for(int i = 0; i < q; i++) { cout << now_v[i] << endl; } } void MQ() { all_str += s + "_"; for(int i = 0; i < q; i++) { start_pos[i] = all_str.size(); all_str += c[i] + "_"; } SuffixArray sa; sa.Build_SA(all_str); LongestCommonPrefixArray lcp(sa); CumulativeSum sum(all_str.size()); vector< int > vect(all_str.size()); for(int i = 0; i < all_str.size(); i++) vect[i] = lcp[i - 1]; RMQ< int > tree; tree.build(begin(vect), end(vect)); for(int i = 0; i < all_str.size(); i++) { if(sa[i] < s.size()) sum.add(i, 1); } sum.build(); for(int i = 0; i < q; i++) { for(int j = a[i]; j <= b[i]; j++) { int pos = start_pos[i] + l[j]; int substr_length = r[j] - l[j] + 1; int low = 0, high = lcp.rank[pos]; while(high - low > 0) { int mid = (low + high) >> 1; if(tree.query(mid + 1, lcp.rank[pos] + 1) >= substr_length) high = mid; else low = mid + 1; } int low2 = lcp.rank[pos] + 1, high2 = all_str.size(); while(high2 - low2 > 0) { int mid = (low2 + high2 + 1) >> 1; if(tree.query(lcp.rank[pos] + 1, mid) >= substr_length) low2 = mid; else high2 = mid - 1; } ans[i] += sum.query(low2 - 1) - sum.query(low - 1); } } for(int i = 0; i < q; i++) { cout << ans[i] << endl; } } int main() { cin >> n >> m >> q >> k; cin >> s; for(int i = 0; i < m; i++) { cin >> l[i] >> r[i]; } for(int i = 0; i < q; i++) { cin >> c[i] >> a[i] >> b[i]; } if(k < q) KQ(); else MQ(); }
Minimum MST Graph
問題概要
頂点 本の重み付き辺, 最小全域木のコストが のグラフで, グラフ全体の重みの和の最小値を求めよ.
解法
よくわからない…( 点)
適当に実験すると, 最小になるときのグラフの辺の重みの種類が高々 種類ということがわかる. で, まだ色々法則がありそうで, 最終的には になるらしいけど, は競技プログラミングではない(過激派, 実験して法則を見つけるという意味ではそうかも).
ソース
#include<bits/stdc++.h> using namespace std; typedef long long int64; const int64 INF = 1LL << 58; int64 G, N, S, M; bool ext; int64 calc(int64 a, int64 b, int64 c, int64 d, bool f) { int64 least = M, res = 0, rest = 0; int64 sum = b * (b - 1) / 2; sum += rest * b; rest += b; res += a * min(least, sum); least -= sum; if(least <= 0) { ext = true; return (res + S); } if(f) { sum = d * (d - 1) / 2; sum += rest * d; res += c * min(least, sum); } return (res + S); } int main() { cin >> G; while(G--) { cin >> N >> M >> S; if(M + 1 == N) { cout << S << endl; continue; } M -= N - 1; int64 PS = N - 1, ret = INF; if(S % PS == 0) { ret = min(ret, calc(S / PS, PS, 0, 0, false)); } { int j = 1; for(int64 i = 1; i < (N > 1000 ? 100000 : S); i++) { int64 sub = S - i; if(sub <= 0) break; if(sub % (PS - j) == 0) { if(i >= sub / (PS - j)) { ret = min(ret, calc(sub / (PS - j), PS - j, i, j, true)); } else { ret = min(ret, calc(i, j, sub / (PS - j), PS - j, true)); } } } } for(int64 j = min(100000LL,PS - 1); j >= 1; j--) { if(S % __gcd(j, PS - j) != 0) continue; for(int64 i = 1; i <= 1; i++) { int64 sub = S - i * j; if(sub % (PS - j) == 0) { if(i >= sub / (PS - j)) { ret = min(ret, calc(sub / (PS - j), PS - j, i, j, true)); } else { ret = min(ret, calc(i, j, sub / (PS - j), PS - j, true)); } } } } for(int64 i = 1; i < min(100000LL,S); i++) { int64 j = i * PS + PS - S; int64 sub = S - i * j; if(PS - j == 0) continue; if(sub < 0) continue; if(j >= PS) continue; if(j < 0) continue; if(sub % (PS - j) == 0) { if(i >= sub / (PS - j)) { if(i == sub / (PS - j)) continue; ret = min(ret, calc(sub / (PS - j), PS - j, i, j, true)); } else { ret = min(ret, calc(i, j, sub / (PS - j), PS - j, true)); } } } cout << ret << endl; } }
Parallelogram Connectivity
問題概要
の六角座標で, 各セルは白か黒で塗られている.
平行四辺形区間内の連結成分の個数を求める 個のクエリが与えられるので全て答えよ.
解法
C/C++ では 秒以内という制約があるが C++14 では適用されないという罠を見つける( 点).
ソース
#include<bits/stdc++.h> using namespace std; int vy[] = {-1, -1, 0, 1, 1, 0}; int vx[] = {0, 1, 1, 0, -1, -1}; int N, M, Q; int a, b, c, d; char S[800][801]; bool s[800][800]; int v[800][800]; inline void dfs(int y, int x, int idx) { v[y][x] = idx; for(int i = 0; i < 6; i++) { int ny = y + vy[i]; int nx = x + vx[i]; if(ny < a || ny > c || nx < b || nx > d) continue; if(s[ny][nx] == s[y][x] && v[ny][nx] != idx) dfs(ny, nx, idx); } } int main() { scanf("%d %d", &N, &M); for(int i = 0; i < N; i++) { scanf("%s", S[i]); for(int j = 0; j < M; j++) { s[i][j] = S[i][j] == 'B'; } } scanf("%d", &Q); for(int k = 1; k <= Q; k++) { scanf("%d %d %d %d", &a, &b, &c, &d); --a, --b, --c, --d; int ret = 0; for(int i = a; i <= c; i++) { for(int j = b; j <= d; j++) { if(v[i][j] != k) dfs(i, j, k), ++ret; } } printf("%d\n", ret); } }