codeforces round 735

B. Cobb

暴力做法
想要最大化 f(i,j)f(i, j),其中 f(i,j)=ijk(aiaj)f(i, j) = i \cdot j - k \cdot (a_i \mid a_j)

  • nn 很小的时候,可以直接暴力求解
  • 考虑到 nn 很大 (105)(10^5) 的情况,注意到 iji \cdot j 这一项是 O(n2)O(n^2),而 k(aiaj)k \cdot (a_i \mid a_j)O(n)O(n) 级别的
    最大化 f(i,j)f(i, j),那么 i,ji, j 一定要尽可能大,i,ji, j 尽可能接近 n\ n,当然 k(aiaj)k \cdot (a_i \mid a_j) 对最大值也有扰动
    也就是说,最大值 MM 会在一个范围内,因为 aiaj2na_i \mid a_j \leqslant 2n
    n(n1)2kn(M=f(n1,n))n(n1)n(n-1)-2kn \leqslant (M = f(n-1, n)) \leqslant n(n-1)
    其他的 i,ji, j 取值,当 i,ji, j 接近 nn 的时候,可能会影响 MM,考虑什么时候会影响到 MM
  • ij2knf(i,j)iji \cdot j - 2kn \leqslant f(i, j) \leqslant i \cdot j,当 ij<n(n1)2kni \cdot j < n(n-1)-2kn 的时候,不论 i,ji, j 怎样取值
    (i,j)(i, j) 都不如 (n1,n)(n-1, n) 更优,所以只有 ijn(n1)2kni \cdot j \geqslant n(n-1)-2kn 的时候,(i,j)(i, j) 可能会影响最优解

    imin(n(n1)2knj)i \geqslant \min \left( \frac{n(n-1)-2kn}{j} \right)

    此时 ii 会对答案有贡献,那么 min(n(n1)2nj)=n(n1)2knn\displaystyle \min \left( \frac{n(n-1)-2n}{j} \right) = \frac{n(n-1)-2kn}{n}

所以当 i[n12k,n]i \in [n-1-2k, n] 的时候,继续暴力求解,整个时间复杂度在 O(k2)O(k^2) 级别

状态压缩 dp

可以考虑枚举 x=aiajx = a_i \mid a_j,问题就转换为如何从 xx 中分离出 i,ji, j
注意分离出的 i,ji, j 一定是最大的两个下标

先遍历 aa 数组,f(x)f(x) 表示使得 ai=xa_i = x最大下标 ii
枚举 x[0,2n]x \in [0, 2n],如果 xx 的二进制 bb 位为 11,那么取 xx 的一个子集 x2x_2
x2=x(1b)x_2 = x \oplus (1 \ll b)x2x_2 至少比 xx 少了一个元素,所以一定是 xx 的子集

观察 x,x2x, x_2,可以发现从状态 x2xx_2 \to x,存在转移
xxx2x_2 至少多一个元素 aia_i,并且 aia_i 的第 bb 位为 11
f(x,0)f(x, 0) 表示能到达状态 xx 的最大下标,f(x,1)f(x, 1) 表示第二大的
这样可以用 {f(x2,0),f(x2,1)}\{f(x_2, 0), f(x_2, 1)\} 来更新 {f(x,0),f(x,1)}\{f(x, 0), f(x, 1)\}
于是可以写出状态压缩 dp

枚举 x[0,2n]x \in [0, 2n],对于 b[0,20],x2=(x(1b))\forall b \in [0, 20], x_2 = (x \oplus (1 \ll b))
{f(x2,0),f(x2,1)}{f(x,0),f(x,1)}\quad\{f(x_2, 0), f(x_2, 1)\} \to \{f(x, 0), f(x, 1)\}
如果 f(x,0)0f(x, 0) \neq 0 并且 f(x,1)0f(x, 1) \neq 0,用 f(x,0)f(x,1)kxf(x, 0) \cdot f(x, 1) - k \cdot x 更新 resres

上述算法思想是
aiaja_i \mid a_j 可以用状态压缩,枚举每一个集合 xx,并且用 xx每一个子集 x2x_2 来更新 xx
对于当前集合 xx(i,j)(i, j) 一定对应集合 xx 中最大的两个数的下标
于是只需要考虑 xxf(x,0)f(x, 0)f(x,1)f(x, 1) 是否合法?
如果合法,用 f(x,0)f(x,1)kxf(x, 0) \cdot f(x, 1) - k \cdot x 来更新全局的 ansans

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const int maxn = 2e5 + 10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int f[maxn][2], n, k;
ll a[maxn], ans = -inf;

void init() {
memset(f, 0, sizeof f);
ans = -inf;
}

inline void upd(const int x, const int x2) {
if (f[x2][0] > f[x][0]) {
int t = f[x][0];
f[x][0] = f[x2][0], f[x][1] = t;
}
else if (f[x2][0] != f[x][0] && f[x2][0] > f[x][1]) f[x][1] = f[x2][0];

if (f[x][1]) assert(f[x][0] != f[x][1]);
}

void pre() {
for (int i = 1; i <= n; i++) {
ll x = a[i];
if (i > f[x][0]) {
f[x][1] = f[x][0], f[x][0] = i;
}
else if (i != f[x][0] && i > f[x][1]) f[x][1] = i;
}
}

void solve() {
for (int x = 0; x <= 2 * n; x++) {
for (int b = 0; b < 20; b++) {
if ((x >> b & 1) == 0) continue;

int x2 = x ^ (1<<b);
upd(x, x2);
}
ll res = 1ll * f[x][0] * f[x][1] - k * x;
if (f[x][0] && f[x][1] && res > ans) ans = res;
}
printf("%lld\n", ans);
}

int main() {
freopen("input.txt", "r", stdin);
int cas;
cin >> cas;

while (cas--) {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
init();

pre();
solve();
}
}