D2. Maximum And Queries (hard version)
题意
给定一个长度为 n n n 的整数数组 a a a 和一个整数 k k k,最多可以执行 k k k 次操作,每次操作:
- 选择 a a a 的一个元素并将其 + 1 +1 +1
求最大的数组按位与,即: ∀ x ∈ a , a n s \forall x \in a,ans ∀x∈a,ans & = x = x =x
思路
这道题需要用到 S O S D P SOS DP SOSDP, c o d e f o r c e s codeforces codeforces 的这篇 blog 有详细教程。
由于 a i ≤ 1 0 6 a_i \leq10^6 ai≤106,所以当 k ≥ ∑ ( 1 0 6 − a i ) k \geq \sum (10^6 - a_i) k≥∑(106−ai) 时,根据高位到低位的原则,最终答案一定大于等于 1 0 6 10^6 106,因此这时候我们可以充分利用已经有的部分 ∑ a i \sum a_i ∑ai,然后将 k k k 次操作平均分配给每个数,使得每个元素的值尽可能接近,最终答案是: a n s = ⌊ k + ∑ a i n ⌋ ans = \lfloor \dfrac{k + \sum a_i}{n} \rfloor ans=⌊nk+∑ai⌋
当
k
<
∑
(
1
0
6
−
a
i
)
k < \sum (10^6 - a_i)
k<∑(106−ai) 时,按照和
D
1
D1
D1 类似的思路贪心从高位到低位拼凑:
对于当前的答案
x
x
x 和当前考虑的位
b
b
b,为了将答案
x
x
x 变为
x
+
2
b
x + 2^b
x+2b,对于某个
a
i
a_i
ai:
-
如果 x ⊈ a i x \not\subseteq a_i x⊆ai,也即是 x x x 不是 a i a_i ai 的子掩码,那么在 b b b 位的更高位,一定已经进行了若干次 + + + 的操作,从而将 a i 原本为 0 的那一位变成 1 a_i 原本为0 的那一位变成1 ai原本为0的那一位变成1,例如 x = 1010 , a i = 1001 x = 1010 , a_i = 1001 x=1010,ai=1001,此时 b b b 为最低位,由于 x ⊄ a i ( x 1 = 1 而 a i 2 = 0 ) x \not\subset a_i (x_1 = 1 而 a_{i_2} = 0) x⊂ai(x1=1而ai2=0),因此在之前的操作中,为了将 a i 2 a_{i_2} ai2 变为 1 1 1,必须经过若干次操作,在这之后, a a a 的最低位一定会变为 0 0 0。
因此这种情况下要操作的次数是: 2 b 2^b 2b -
若 x + 2 b ⊆ a i x + 2^b \subseteq a_i x+2b⊆ai,那么说明 a i b = 1 a_{i_b} = 1 aib=1,这种情况不需要操作
-
其他情况下,也即是 x ⊆ a i ⋀ x + 2 b ⊈ a i x \subseteq a_i \bigwedge x+2^b \not\subseteq a_i x⊆ai⋀x+2b⊆ai,这种情况下 a i b = 0 a_{i_b} = 0 aib=0,需要的操作数是 2 b − a i m o d 2 b 2^b - a_i mod 2^b 2b−aimod2b,也即是充分利用 a i a_i ai 的更低位的那些已经有的值,节约操作次数
通过上述的三种情况,不难发现我们需要预处理两种信息:
- c n t [ m a s k ] cnt[mask] cnt[mask] : 在数组 a a a 中有多少个值 a i a_i ai 满足 m a s k ⊆ a i mask \subseteq a_i mask⊆ai,也就是 m a s k mask mask 有多少个 父掩码
- d p s u m [ m a s k ] [ b ] dpsum[mask][b] dpsum[mask][b] : 对于 m a s k mask mask 的所有 父掩码 x x x,对他们模 2 b 2^b 2b 的值求和
上面两种信息可以用
S
O
S
SOS
SOS
D
P
DP
DP 预处理得到:
与
b
l
o
g
blog
blog 里的不同,这里是对于一个
m
a
s
k
mask
mask 归并父掩码的信息,因此判断某一位
i
i
i 的时候,
i
f
if
if 语句要反过来:
if(!(mask & (1<<i)))
如果这一位是 0 0 0,那么它父亲这一位可以是 0 0 0 或 1 1 1,因此要加上低 i − 1 i-1 i−1 位的信息
fore(i,0,20)
fore(mask,0,1<<20)
if(!(mask & (1<<i)))
cnt[mask] += cnt[mask^(1<<i)];
对于 d p s u m dpsum dpsum 的求解也是类似。
最后对于每次询问,第一种情况需要的操作次数是:
2
b
⋅
(
n
−
c
n
t
[
a
n
s
]
)
2^b \cdot (n-cnt[ans])
2b⋅(n−cnt[ans])
第三种情况需要的操作次数是:
2
b
⋅
(
c
n
t
[
a
n
s
]
−
c
n
t
[
a
n
s
∣
2
b
]
)
−
(
d
p
s
u
m
[
a
n
s
]
[
b
]
−
d
p
s
u
m
[
a
n
s
∣
2
b
]
[
b
]
)
2^b \cdot(cnt[ans] - cnt[ans|2^b]) - (dpsum[ans][b] - dpsum[ans|2^b][b])
2b⋅(cnt[ans]−cnt[ans∣2b])−(dpsum[ans][b]−dpsum[ans∣2b][b])
两种情况加起来就是代码中的表达式了。
完整代码:
// Problem: D2. Maximum And Queries (hard version)
// Contest: Codeforces - Codeforces Round 912 (Div. 2)
// URL: https://codeforces.com/contest/1903/problem/D2
// Memory Limit: 512 MB
// Time Limit: 7000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
const int INF=0x3f3f3f3f;
const long long INFLL=0x3f3f3f3f3f3f3f3fLL;
typedef long long ll;
const int N = 1000050;
ll cnt[1<<20]; //how many elements from the array is mask a submask of
ll dpsum[1<<20][20]; //sum of a_i mod 2^b over all ai for which x is a submask
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n,q;
std::cin>>n>>q;
std::vector<int> a(n+1);
ll div = 0 , sum = 0;
fore(i,1,n+1){
std::cin>>a[i];
sum += a[i];
div += ((1ll<<20) - a[i]);
++cnt[a[i]]; //本身就是自己的父掩码
ll val = 0;
fore(j,0,20){
dpsum[a[i]][j] += val; //本身就是自己的父掩码
val += (a[i] & (1ll<<j));
}
}
fore(i,0,20)
fore(mask,0,1<<20)
if(!(mask & (1<<i)))
cnt[mask] += cnt[mask|(1<<i)];
fore(i,0,20)
fore(mask,0,1<<20)
if(!(mask & (1<<i)))
fore(p,0,20)
dpsum[mask][p] += dpsum[mask|(1<<i)][p];
while(q--){
ll k;
std::cin>>k;
if(k >= div){
std::cout<<(k+sum)/n<<endl;
continue;
}
ll ans = 0;
for(int i=19;i>=0;--i){
ll opt = (n - cnt[ans|(1<<i)]) * (1ll<<i) - (dpsum[ans][i] - dpsum[ans|(1<<i)][i]);
if(opt <= k){
k -= opt;
ans |= 1<<i;
}
}
std::cout<<ans<<endl;
}
return 0;
}