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

AOJ 0231 Dangerous Bridge

解法

高々100人の情報しかないので、座標圧縮すると最大でも200回ループ回せば終わる。

ソースコード

struct P {
    int m;
    long long a, b;
};

int n, from[128], to[128], sum[128];
P p[128];

int main() {
    while(scanf("%d", &n) && n) {
        string ans = "OK";
        memset(sum, 0, sizeof sum);
        vector<long long> x;

        for(int i = 0; i < n; i++) {
            scanf("%d%lld%lld", &(p[i].m), &(p[i].a), &(p[i].b));
            x.push_back(p[i].a); x.push_back(p[i].b);
        }

        sort(x.begin(), x.end());
        x.erase(unique(x.begin(), x.end()), x.end());

        for(int i = 0; i < n; i++) {
            from[i] = lower_bound(x.begin(), x.end(), p[i].a) - x.begin();
            to[i] = lower_bound(x.begin(), x.end(), p[i].b) - x.begin();
        }

        for(int i = 0; i < n; i++) {
            for(int j = from[i]; j < to[i]; j++) {
                sum[j] += p[i].m;
            }
        }

        for(int i = 0; i < x.size(); i++) {
            if(sum[i] > 150) {
                ans = "NG";
                break;
            }
        }

        cout << ans << endl;
    }
}

やっと300問…。部活に忙しい毎日…