CF1868B2 Candy Party (Hard Version)

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

思路

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

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

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

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

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

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

需要的数组 \(m_i\)\(giv_i\)\(rec_i\)

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

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

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

\(rec_i\) 代表可以直接收 \(2^i\) 个糖果,与 \(giv_i\) 基本相同。

那么我们的目标就是尽可能地使用 \(giv_i\)\(rec_i\)(拆开或者不拆),来让所有的 \(m_i\) 都变成 \(0\)

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

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

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

最后再看 \(m_0\) 是否为 \(0\) 即可。

另外,因为涉及各种数量相加,而有些 \(2^k\) 可能在某些情况没有出现,所以不好再用 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;
}


posted @ 2023-09-11 14:58  One_JuRuo  阅读(102)  评论(0编辑  收藏  举报