import std; void main () { int N, M, K; readln.read(N, M, K); auto A = readln.split.to!(int[]); // dp[i][j][k] := 先頭i枚からj枚引いて、値kを実現できるか? // これでO(NK max(A)) // どう見ても無理なので、違ったアプローチが必要そう。 // K / 2枚まで、すべての組み合わせを列挙することならできる。 // 任意のとり方がこの前計算の値によって表現可能。 // これならできそうか auto vals = new long[][](4); void f (ref long[] x, int cur, long val, int rem) { if (rem == 0) { x ~= val; return; } if (cur == N) { return; } // 取る f(x, cur, val + A[cur], rem - 1); f(x, cur + 1, val + A[cur], rem - 1); // 取らない f(x, cur + 1, val, rem); } foreach (i; 0 .. 4) { f(vals[i], 0, 0, i); } foreach (i; 0 .. 4) { vals[i].sort; } long ans = 0; foreach (i; 0 .. 4) { foreach (j; 0 .. 4) { if (K < i + j) { continue; } foreach (v; vals[i]) { if (M < v) { continue; } auto ok = vals[j].arrayBSearch!((long x) => v + x <= M)[0]; if (0 < ok.length) { ans = max(ans, v + ok[$ - 1]); } } } } writeln(ans); } void read (T...) (string S, ref T args) { import std.conv : to; import std.array : split; auto buf = S.split; foreach (i, ref arg; args) { arg = buf[i].to!(typeof(arg)); } } // 配列特化二分探索 // fun: T -> boolが必要。また、一度falseになったら後ろが必ずfalseになるような述語を仮定。 // 戻り値はスライス2個。 // それぞれB1, B2としたとき、 // B1の要素はfun(b1)がtrue // B2の要素はfun(b2)がfalse import std.traits; T[][2] arrayBSearch (alias fun, T) (T[] A) if (__traits(compiles, fun(T.init)) && is(typeof(fun(T.init)) == bool) ) { if (A.length == 0) { return [[], []]; } if (!fun(A[0])) { return [[], A[]]; } int l = 0, r = cast(int)(A.length); while (1 < r - l) { int mid = (l + r) / 2; if (fun(A[mid])) { l = mid; } else { r = mid; } } return [A[0 .. r], A[r .. $]]; }