Code Festival 2014 予選A
精神的にくるものがあったので久しぶりに記事を書く。
予選開始
- A問題開く。通す。
- B問題開く。通す。
- C問題開く。通す。
- D問題開く。貪欲に上から決めていけば解けそう…。
- 少し悩んでも良い実装が思い浮かばない。
- しょうがなく桁DPを書く。
- 提出する。WA...
- 原因がわからないまま終了。
反省
D問題、どうせ通るし30点取りに行かなくてもいいかなと思ってたら嵌って本選に行けない順位になってしまった。戦略ミスだなぁ。死にたさ極まる。
A問題
int main() { string s; cin >> s; s += "2014"; cout << s << endl; }
B問題
int main() { string A; long long B; cin >> A >> B; cout << A[(B - 1) % A.size()] << endl; }
C問題
int f(int x) { return x / 4 - x / 100 + x / 400; } int main() { int A, B; cin >> A >> B; cout << f(B) - f(A - 1) << endl; }
D問題
はまったところは2つあった。
- 10000 1のようなケースを想定できてなかった。
- 全ての状態を考えずにdpの処理部書いてた。
あと、添字ミスもあったしホント辛くなりますよ〜…。
const long long INF = 1e16; const int M = 10; long long dp1[20][4][1 << 12]; long long dp2[20][4][1 << 12]; int main() { string A; long long K, ans = INF; cin >> A >> K; int m = A.size(); for(int i = 0; i < m; i++) { for(int k = 0; k < (1 << M); k++) { for(int b = 0; b < 2; b++) { dp1[i][b][k] = INF; dp2[i][b][k] = INF; } } } for(int i = 0; i < 10; i++) { int n = A[0] - '0'; int S = (i == 0 ? 0 : 1 << i); if(n - i >= 0) dp1[0][(n - i) > 0][S] = n - i; if(i - n >= 0) dp2[0][(i - n) > 0][S] = i - n; } // pos for(int i = 1; i < m; i++) { int n = A[i] - '0'; // carry bit for(int b = 0; b < 2; b++) { // used number for(int k = 0; k < (1 << M); k++) { // next number for(int l = 0; l < 10; l++) { long long borrow = 0; long long add = n - l; bool c = add > 0; int S = (l == 0 && k == 0 ? 0 : k | (1 << l)); int count = __builtin_popcount(S); if(count > K) { continue; } if(add >= 0 && b) { dp1[i][0][S] = min(dp1[i][0][S], (dp1[i-1][b][k] + borrow) * 10LL + add); dp1[i][1][S] = min(dp1[i][1][S], (dp1[i-1][b][k] + borrow) * 10LL + add); } if(add < 0 && !b) continue; if(add < 0) { add += 10; c = add > 0; borrow = -1; } dp1[i][c][S] = min(dp1[i][c][S], (dp1[i-1][b][k] + borrow) * 10LL + add); } for(int l = 0; l < 10; l++) { long long borrow = 0; long long add = l - n; bool c = add > 0; int S = (l == 0 && k == 0 ? 0 : k | (1 << l)); int count = __builtin_popcount(S); if(count > K) { continue; } if(add >= 0 && b) { dp2[i][0][S] = min(dp2[i][0][S], (dp2[i-1][b][k] + borrow) * 10LL + add); dp2[i][1][S] = min(dp2[i][1][S], (dp2[i-1][b][k] + borrow) * 10LL + add); } if(add < 0 && !b) continue; if(add < 0) { add += 10; c = add > 0; borrow = -1; } dp2[i][c][S] = min(dp2[i][c][S], (dp2[i-1][b][k] + borrow) * 10LL + add); } } } } for(int j = 0; j < 2; j++) { for(int k = 0; k < (1 << M); k++) { ans = min(ans, dp1[m-1][j][k]); ans = min(ans, dp2[m-1][j][k]); } } cout << ans << endl; }
JOI 2012-2013 本選 問題1 電飾
反転回数は最大でも1回かと思っていたけど2回だった。反省
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 前回反転させた, 前回のランプ, 場所 int dp[3][2][100001]; int main() { int n, k, ans = 0; vector<bool> a; cin >> n; for(int i = 0; i < n; i++) { cin >> k; a.push_back(k); } dp[0][a[0]][0] = dp[1][!a[0]][0] = 1; for(int i = 1; i < n; i++) { dp[0][a[i]][i] = max(dp[0][a[i]][i], dp[0][!a[i]][i-1] + 1); dp[1][!a[i]][i] = max(dp[1][!a[i]][i], dp[1][a[i]][i-1] + 1); dp[1][!a[i]][i] = max(dp[1][!a[i]][i], dp[0][a[i]][i-1] + 1); dp[2][a[i]][i] = max(dp[2][a[i]][i], dp[2][!a[i]][i-1] + 1); dp[2][a[i]][i] = max(dp[2][a[i]][i], dp[1][!a[i]][i-1] + 1); ans = max(ans, dp[0][a[i]][i]); ans = max(ans, dp[1][!a[i]][i]); ans = max(ans, dp[2][a[i]][i]); } cout << ans << endl; }
JOI 2013-2014 予選4番
最近forで回すDPの方が書きやすい、と感じている
最初、鍵を持っている人の情報を持ってしまっていて重複したケースも数え上げてしまっていて時間が結構かかった。
#include <iostream> #include <cstdio> #include <string> #include <vector> #include <map> #include <algorithm> using namespace std; const int MOD = 10007; // J = 0bit, O = 1bit, I = 2bit // i日目, 参加した人(bit表現) int dp[1001][12]; int main() { map<char, int> D; D['J'] = 0, D['O'] = 1, D['I'] = 2; int N, ans = 0; string S; cin >> N >> S; for(int i = 0; i < 1 << 3; i++) { if(((i >> D[S[0]]) & 1) && ((i >> D['J']) & 1)) { dp[0][i] = 1; } } for(int i = 1; i < N; i++) { for(int j = 0; j < 1 << 3; j++) { for(int k = 0; k < 1 << 3; k++) { for(int l = 0; l < 3; l++) { if((k >> D[S[i]] & 1) && ((k >> l) & 1) && ((j >> l) & 1)) { dp[i][k] += dp[i-1][j]; dp[i][k] %= MOD; break; } } } } } for(int i = 0; i < 1 << 3; i++) { ans += dp[N-1][i]; ans %= MOD; } cout << ans << endl; }
PKU 1036
少し読解困難だった.
はTLEするが,の処理は外に出すことができるためとなる.
dp[i][j] := i時間で扉がjの状態であるときに,来訪するgang達の価値の総和の最大値
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <algorithm> #include <vector> #include <string> #include <map> using namespace std; const int INF = 1 << 30; int dp[2][101]; int main() { int N, K, T, t[101], p[101], s[101], ans = 0; cin >> N >> K >> T; for(int i = 0; i < N; i++) { scanf("%d", &t[i]); } for(int i = 0; i < N; i++) { scanf("%d", &p[i]); } for(int i = 0; i < N; i++) { scanf("%d", &s[i]); } for(int i = 0; i < 2; i++) for(int j = 0; j <= K; j++) dp[i][j] = -INF; dp[0][0] = 0; for(int i = 1; i <= T; i++) { int n = i & 1; int c = !n; for(int j = 0; j <= K; j++) { dp[n][j] = dp[c][j]; if(j+1 <= K) dp[n][j] = max(dp[n][j], dp[c][j+1]); if(j-1 >= 0) dp[n][j] = max(dp[n][j], dp[c][j-1]); } for(int j = 0; j < N; j++) { if(i != t[j]) continue; dp[n][s[j]] += p[j]; } } for(int i = 0; i <= K; i++) { ans = max(ans, dp[T&1][i]); } cout << ans << endl; }
JMC 2014 4番
pastaっぽいと思った。作問のプロだと感じた(小並感)
#include <iostream> #include <cstdio> #include <string> #include <vector> #include <map> #include <algorithm> using namespace std; int dp[128][128][2]; bool m[128][128]; const int MOD = 100000; int main() { int N, M, L, a, b; cin >> N >> M >> L; for(int i = 0; i < L; i++) { cin >> a >> b; a--, b--; m[a][b] = true; } for(int i = 0; i < M; i++) { if(!m[0][i]) dp[0][i][0] = 1; } for(int i = 0; i < N - 1; i++) { for(int j = 0; j < M; j++) { for(int k = 0; k < 2; k++) { if(k == 0) { if(m[i + 1][j]) continue; dp[i + 1][j][!k] += dp[i][j][k]; dp[i + 1][j][!k] %= MOD; } else { for(int l = 0; l < M; l++) { if(m[i + 1][l]) continue; a = (l == j ? k : !k); dp[i + 1][l][a] += dp[i][j][k]; dp[i + 1][l][a] %= MOD; } } } } } int ans = 0; for(int i = 0; i < M; i++) { ans += dp[N - 1][i][1]; ans %= MOD; } cout << ans<< endl; }
PKU 1014
JOI予選が近づいてきて,JOI予選の過去問解いてない後輩を煽るためPKUのDP問題解いてます(意味不明)
AOJ 0271: Izua Dictionary
解説
文字列をsとして愚直にシミュレーションしていくと、sを適当に入れ替えたときのi番目(1 <= i <= n)の文字の位置を求めるのに、i番目までに使った文字が影響される。よって最初にBITで[1, n]に1を加えておき、s[i]の場所ごとにiを0にしていくことで、sum(i)でi番目の文字の位置を求めることができる。(日本語難しすぎ)
割とゴリ押しな感じあふれてる。
ソースコード
めっちゃ汚い…
typedef long long i64; const i64 MOD = 1000000007; const int MAX_N = 100002; struct BinaryIndexedTree { int data[MAX_N]; BinaryIndexedTree() { memset(data, 0, sizeof data); } // [1, n] i64 sum(i64 i) { i64 ret = 0; for(; i > 0; i -= i & -i) { ret += data[i]; } return ret; } void add(i64 i, i64 x) { for(; i < MAX_N; i += i & -i) { data[i] += x; } } }; int main() { int pair[MAX_N]; i64 ans; i64 fact[MAX_N]; fact[0] = 1; for(int i = 1; i < MAX_N; i++) { fact[i] = i * fact[i - 1]; fact[i] %= MOD; } int n, r; while(cin >> n, n) { cin >> r; ans = 0; BinaryIndexedTree b; for(int i = 0; i < MAX_N; i++) { b.add(i + 1, 1); pair[i] = i; } for(int i = 0, a, b; i < r; i++) { cin >> a >> b; swap(pair[a-1], pair[b-1]); } for(int i = 0; i < n; i++) { i64 x = b.sum(pair[i] + 1) - 1; b.add(pair[i] + 1, -1); ans += x * fact[n - i - 1]; ans %= MOD; } cout << ans << endl; } }