最初、罠に引っかかった
問題概要
長さ の数列 が与えられる。これらの連続する区間の総和を書き出していく ( 個ある)。
これらの中から 個選んで AND をとった値として考えられる最大値を求めよ。
制約
嘘貪欲
とりあえず なので、 個の整数を選んでくることはできる。それらを改めて とする。
で、 個選ぶ方法を最適化するなんてのは、とりうる解法はそんなに多くない。なんらかの尺度で良い順に選ぶとか、そういう Greedy になることがほとんど (最近他のパターンとして Alien DP があるのを知った)
で、最初は「単純に 個の中から大きい順に 個でいいんじゃないかな」と思ってしまった。というのも、AND をとるというのは基本的に値がどんどん小さくなってしまう。なので、なるべく大きい数同士で AND をとるのがいいんじゃないかなと。
しかし、例外があった。 で が
- 1000
- 1001
- 0111
- 0110
- 0101
とかの場合、大きい順にやると 0 になってしまう。でも、下 3 個を選べば 100 になる。
上の桁から辞書順 Greedy
この手の問題は二進数で考えるのが定石だった。上の桁から順番に 1 にできるかどうかを見ていく Greedy でよかった。
- まず、 とかにして、 桁目に 1 が立っているのが 個あったら、AND 和の 桁目も にできるので、そうすることにする (そして 桁目が 1 でないものは捨てる)
- 個未満だったら、 桁目を 1 にすることは諦めて、とくに捨てることはしない
という風にすればよい。そして捨てられずに残ったやつで、次は 桁目について同じことをして...という風にすれば OK。計算量は 。
#include <iostream> #include <vector> using namespace std; int N, K; vector<long long> a; int main() { cin >> N >> K; a.resize(N); for (int i = 0; i < N; ++i) cin >> a[i]; vector<long long> S(N+1, 0); for (int i = 0; i < N; ++i) S[i+1] = S[i] + a[i]; vector<long long> v; for (int i = 0; i < N; ++i) for (int j = i+1; j <= N; ++j) v.push_back(S[j] - S[i]); long long res = 0; vector<bool> dame(v.size(), false); for (int d = 60; d >= 0; --d) { int num = 0; for (int i = 0; i < v.size(); ++i) { if (dame[i]) continue; if (v[i] & (1LL<<d)) ++num; } if (num >= K) { res += (1LL<<d); for (int i = 0; i < v.size(); ++i) { if ( !(v[i] & (1LL<<d)) ) dame[i] = true; } } } cout << res << endl; }