2014-2015 ACM-ICPC, NEERC, Moscow Subregional Contest J. JPEG is Awesome!
問題
K日間にわたって写真を撮る。カメラの記憶容量はLであり、写真一枚が消費する記憶容量は非圧縮の場合Dである。
i日目にはti枚の写真を撮り、j番目の写真のできばえはQijである。
写真は好きなものを好きなだけ削除することができる。
また、同じ日の写真は同じ圧縮率α(0≦α≦1)で圧縮することができる。
圧縮した場合、記憶容量はα倍、できばえはα倍になる。
同じ日の写真を、違う圧縮率で圧縮することはできない。
できばえの合計値の最大値を求めよ。
出力は分数の形で出力せよ。
制約条件
K≦10^6
L, D≦10^9
写真の合計枚数≦10^9
方針
0. 同じ日の中で写真はできばえの良い順に使うとしてよい。
1. 圧縮する日は高々一日だけでよい。
2. 非圧縮の場合の容量の和はL + D未満であるとしてよい
1. 最適解において圧縮した日が二日あるなら、1容量あたりのできばえが多い日のαを大きくすれば得をするのでこれは最大性に反する。
2. 最適解において、L + D以上なら、圧縮している日の最後の一枚を削ればできばえが増加する。
なので、i日目を圧縮すると仮定し、i日目にj枚使うとするのを全通り試せばよい。
2.より採用する写真の枚数(m)はあらかじめわかっているので、
i日目にj枚使うとすると、i日以外では貪欲にいい順にm - j枚とればよい。
これは適切なデータ構造(BITなど)を使えばO(log(n))時間でできる。
分数のところは多倍長とか使って頑張る。
ソースコード
const int MX = 1000010; ll bit[MX], bit2[MX]; inline void add(ll *bit, int i, ll x){ for(i++ ; i < MX; i += i & -i) bit[i] += x; } inline ll sum(ll *bit, int i){ ll res = 0; for(i++; i; i -= i & -i) res += bit[i]; return res; } inline int get(ll *bit, int k){ if(k == 0) return -1; if(sum(bit, MX - 2) < k) return -inf; int lo = 0, hi = 1 << 32 - __builtin_clz(MX), mid; while(lo + 1 < hi){ mid = (lo + hi) / 2; if(mid > MX || bit[mid] >= k) hi = mid; else k -= bit[lo = mid]; } return lo; } ll sums[MX]; int K, L, D, delta; vector<vi> v, pos; inline void calc(ll o, ll cursum, int j, ll &d, ll &x, ll &y){ d = o; x = cursum; y = (j + 1ll) * D; ll g = __gcd(x, y), z = ((j + 1ll) * D - delta); x /= g; y /= g; g = __gcd(z, y); z /= g; y /= g; if(x * 1.0 * z <= 1e18){ x *= z; d += x / y; x %= y; } else{ BigNum a = convert(x), c = convert(z); pair<BigNum, Int> m = divmod(a * c, y); stringstream ss; ss << m.first; d += atoll(ss.str().c_str()); x = m.second; } } int main(){ scanf("%d%d%d", &K, &L, &D); v.resize(K); pos.resize(K); vector<pi> in; rep(i, K){ int t, u; scanf("%d", &t); rep(j, t) scanf("%d", &u), v[i].pb(-u); sort(all(v[i])); } random_shuffle(all(v)); rep(i, K){ rep(j, v[i].size()) in.pb(mp(v[i][j], i)); } sort(all(in)); int n = in.size(); rep(i, n){ pos[in[i].second].pb(i); sums[i + 1] = sums[i] - in[i].first; add(bit, i, 1); } ll ans = sums[min(n, L / D)]; int usecnt = (L + D - 1) / D; delta = (ll)usecnt * D - L; long double ansd = ans; ll d = ans, x = 0, y = 1; #if 0 rep(i, n) cerr<<-in[i].first<<(i==n-1?"\n":" "); rep(i, n) cerr<<in[i].second<<(i==n-1?"\n":" "); rep(i, K) rep(j, pos[i].size()) cerr<<pos[i][j]<<(j==pos[i].size()-1?"\n":" "); dbg(usecnt); dbg(delta); dbg(ansd); #endif if(usecnt > n){ cout << ans << endl; return 0; } assert(usecnt <= n); assert(delta < D); assert(n <= 1000000); vi ord; rep(i, K) ord.pb(i); random_shuffle(all(ord)); rep(ii, K){ int i = ord[ii]; vi &u = v[i]; int t = u.size(); ll cursum = 0; assert(sum(bit2, MX - 3) == 0); assert(sum(bit, MX - 3) == n); rep(j, t){ add(bit2, pos[i][j], -u[j]); add(bit, pos[i][j], -1); } rep(j, t){ if(j >= usecnt) break; int p = get(bit, usecnt - j - 1); cursum -= u[j]; //dbg(j); dbg(p); if(p == -inf) continue; /* dbg(n - t + j + 1); dbg(usecnt); */ assert(n - t + j + 1 >= usecnt); ll o = sums[p + 1] - sum(bit2, p); long double tmp = o + 1.0l * cursum * ((j + 1ll) * D - delta) / (j + 1) / D; assert(o <= ans); if(ansd >= tmp + EPS) continue; ll dd, xx, yy; calc(o, cursum, j, dd, xx, yy); if(d < dd || d == dd && convert(x) * yy < convert(xx) * y){ ansd = tmp; d = dd; x = xx; y = yy; } assert(x < y); assert(__gcd(x, y) == 1); } rep(j, t){ add(bit2, pos[i][j], u[j]); add(bit, pos[i][j], 1); } } cout << d; if(x) cout << " + " << x << "/" << y; cout << endl; dbg((double)ansd); return 0; }