Codeforces Round 912 (Div. 2) D2. Maximum And Queries (hard version) 【SOSDP】

D2. Maximum And Queries (hard version)

D2

题意

给定一个长度为 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 xa,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 ai106,所以当 k ≥ ∑ ( 1 0 6 − a i ) k \geq \sum (10^6 - a_i) k(106ai) 时,根据高位到低位的原则,最终答案一定大于等于 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<(106ai) 时,按照和 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 xai,也即是 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) xai(x1=1ai2=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+2bai,那么说明 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 xaix+2bai,这种情况下 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 2baimod2b,也即是充分利用 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 maskai,也就是 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 i1 位的信息

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(ncnt[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[ans2b])(dpsum[ans][b]dpsum[ans2b][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; 
}
  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值