2016-2017 National Taiwan University World Final Team Selection Contest
結構前のコンテストですが、G問題がシンプルだけどどこか新鮮で面白かったので紹介します
Dashboard - 2016-2017 National Taiwan University World Final Team Selection Contest - Codeforces
概要
N個(値段:p_i)の空でない部分集合であってK番目に値段の和が小さいものを求めよ(復元なし)
2 ≤ N ≤ 2 × 10^5
1 ≤ K ≤ min(10^6, 2^N - 1)
1 ≤ p_i ≤ 10^8
解法
まずpを小さい順にソートします。もちろん2^N通り全列挙は出来ないのと、Kはあまり大きくないので、小さい順に列挙することを考えます。
ここで、ある集合の1番右のものの次を続けて使うorある集合の1番右をやめてその右隣を使うという遷移をすることにすると、価値が単調に増加するように遷移出来て嬉しいことがわかります。(イメージしにくいですが)
よって、priority_queue等で管理しつつ遷移していくことでO(KlogK)で解くことが出来ました。
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> pii; typedef long long ll; typedef vector<int> vi; #define pb push_back #define eb emplace_back #define mp make_pair #define fi first #define se second #define rep(i,n) rep2(i,0,n) #define rep2(i,m,n) for(int i=m;i<(n);i++) #define ALL(c) (c).begin(),(c).end() const int MN = 200010; int N, K; int p[MN]; int main() { scanf("%d %d", &N, &K); rep(i, N) scanf("%d", &p[i]); sort(p, p + N); using Data = pair<ll, int>; priority_queue<Data, vector<Data>, greater<Data>> que; que.push(mp(0LL, -1)); ll la = -1; ++K; while (K--) { Data t = que.top(); que.pop(); ll val = t.fi; int pos = t.se; la = val; if (pos == N-1) continue; //add next que.push(mp(t.fi + p[pos + 1], pos + 1)); //replace if (pos != -1) { que.push(mp(t.fi + p[pos + 1] - p[pos], pos + 1)); } } cout << la << endl; return 0; }