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

少し読解困難だった.
O(NKT)はTLEするが,Nの処理は外に出すことができるためO((N+K)T)となる.

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問題解いてます(意味不明)

問題概要

価値が1から6まであるビー玉がある.入力には各々の価値のビー玉の個数が与えられている.二人はビー玉を任意の個数選び,ビー玉の価値を二等分できるかどうか判定する.

 

続きを読む

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;
	}
}