CF1868B2 Candy Party (Hard Version)

去洛谷看我的博客

建议先看简单版本以及我的题解

思路

可以发现困难版本比简单版本唯一不一样的地方就是可以给糖也可以不给,可以收糖也可以不收。

首先还是需要求和,如果无法平分,肯定无解,再算出平均数 s s s

还是考虑每一个 a i a_i ai,如果 ∣ a i − s ∣ |a_i-s| ais 不是二次幂,那么肯定必须同时给糖和收糖,判断有没有解还是和简单版本一样。

不一样的地方就是 ∣ a i − s ∣ |a_i-s| ais 是二次幂了,不同于简单版本,这里可以直接给或者收 ∣ a i − s ∣ |a_i-s| ais 这么多的糖了,而不像简单版本,必须给或者收 2 × ∣ a i − s ∣ 2\times |a_i-s| 2×ais 颗糖,再收或者给 ∣ a i − s ∣ |a_i-s| ais 颗糖。

因为存在必须拆成 2 x − 2 y 2^x-2^y 2x2y 形式的 a i a_i ai,所以可以考虑先把这部分和本身是二次幂的分开统计。

这里使用三个数组进行储存,如下:

需要的数组 m i m_i mi g i v i giv_i givi r e c i rec_i reci

其中, m i m_i mi 统计必须拆成两个二次幂的情况,等价于简单版本的两个 map,只不过是把原来的 2 k 2^k 2k 改成了 k k k 方便储存,同时直接计算差值。可以简单的理解为 m [ k ] = m 1 [ 2 k ] − m 2 [ 2 k ] m[k]=m1[2^k]-m2[2^k] m[k]=m1[2k]m2[2k]

那么, m i m_i mi 如果大于 0 0 0,代表还有若干个 2 i 2^i 2i 的糖果数需要给,小于 0 0 0,代表还有若干个 2 i 2^i 2i 的糖果数被需要。

其次, g i v i giv_i givi 代表可以直接给 2 i 2^i 2i 个糖果的个数,这些给法也可以拆开给。

r e c i rec_i reci 代表可以直接收 2 i 2^i 2i 个糖果,与 g i v i giv_i givi 基本相同。

那么我们的目标就是尽可能地使用 g i v i giv_i givi r e c i rec_i reci(拆开或者不拆),来让所有的 m i m_i mi 都变成 0 0 0

因为对于某个可以直接给 2 k 2^k 2k 个糖果的情况,有两种方式,即给 2 k 2^k 2k 和 给 2 k + 1 2^{k+1} 2k+1 2 k 2^k 2k,都只会影响 2 k 2^k 2k 2 k + 1 2^{k+1} 2k+1,所以我们考虑从 k k k 大的情况开始枚举。

首先对于每个的 i i i,我们都在枚举 i + 1 i+1 i+1 时,保证了 m i + 1 = 0 m_{i+1}=0 mi+1=0,那么剩余的 g i v i giv_i givi r e c i rec_i reci 统计的个数都不能拆开,那么 m i m_i mi 就会变成 m i + g i v i − r e c i m_i+giv_i-rec_i mi+givireci 表示新的需要给/收的糖果数为 2 k 2^k 2k 的个数。

如果此时新的 m i m_i mi 已经为 0 0 0,那么就不需要去管了,如果 m i m_i mi 小于 0 0 0,代表还需要给若干个给 2 k 2^k 2k 的糖果数,只能拆 2 k − 1 2^{k-1} 2k1 去补,那么更新 g i v i − 1 giv_{i-1} givi1 m i − 1 m_{i-1} mi1,如果此时 g i v i − 1 < 0 giv_{i-1}<0 givi1<0,就代表无法使 2 k 2^k 2k 的糖果数达成平衡,一定无解,否则的话就继续枚举下一个 i i i m i m_i mi 大于 0 0 0 的情况同理。

最后再看 m 0 m_0 m0 是否为 0 0 0 即可。

另外,因为涉及各种数量相加,而有些 2 k 2^k 2k 可能在某些情况没有出现,所以不好再用 map 直接存数量,所以这里才转化了一下,使得可以使用数组储存。

AC code

#include<bits/stdc++.h>
using namespace std;
long long T,n,a[200005],sum,k,low,m[35],giv[35],rec[35];
inline long long lowbit(long long x){return x&(-x);}
inline long long get(long long x)//对于2^k,取出k
{
	long long ans=0;
	while(x%2==0) x/=2,++ans;
	return ans;
}
inline bool sol()
{
	scanf("%lld",&n),sum=0;
	for(long long i=1;i<=n;++i) scanf("%lld",&a[i]),sum+=a[i];
	sum/=n;
	for(long long i=0;i<=30;++i) m[i]=giv[i]=rec[i]=0;//多测要清空
	for(long long i=1;i<=n;++i)
	{
		a[i]-=sum;k=abs(a[i]);low=lowbit(k);
		if(k==low){if(a[i]>0) ++giv[get(k)];else if(a[i]<0) ++rec[get(k)];continue;}//如果本身就是二次幂,统计到giv或者rec
		k+=low;
		if(k!=lowbit(k)) return 1;
		if(a[i]>0) ++m[get(k)],--m[get(low)];
		else if(a[i]<0) ++m[get(low)],--m[get(k)];//统计个数
	}
	for(long long i=30;i>=0;--i)
	{
		m[i]+=(giv[i]-rec[i]),k=abs(m[i]);//i剩余的giv和rec只能用于i,不能拆开,防止更改i+1
		if(!i){if(!m[0]) return 0;else return 1;}//对于0就不能用i-1去平衡i,而是直接判断
		if(m[i]<0)//还需要收若干个2^i
		{
			giv[i-1]-=k,m[i-1]-=k;//拆2^(i-1)去补2^i
			if(giv[i-1]<0) return 1;//2^(i-1)数量不够就无解
		}
		else if(m[i]>0)//同上面一种情况
		{
			rec[i-1]-=k,m[i-1]+=k;
			if(rec[i-1]<0) return 1;
		}
	}
}
int main()
{
	scanf("%lld",&T);
	while(T--)
		if(sol()) printf("NO\n");
		else printf("YES\n");
	return 0;
}


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值