MP3
题目连接:https://codeforc.es/contest/1199/problem/C
题意
有一个文本,里面有很多数字 ai ,但是你的内存只有 I byte =8I bit,每一类不同的数字占一个bit,你每次只能对其中一个数字进行操作,增加数字或者减少数字,最终做到所有的数字在某个范围内[L,R],满足 k=log2(K) 向上取整,K为当前的区间长度,nk<=8*I(即这段连续的区间能被你的内存装下,n为数字的个数),要求用最少的操作次数,满足有足够的内存装下这段这段数字。
输入
两个数 n ,I 表示数字的个数,I 为内存大小( 1<=n<=400000,1<=I<=1e8 ),第二行输入n个数字ai,( 0<=ai<=1e9 )
输出
最小的操作次数
思路
离散化+前缀和+尺取法
对题目进行分析,所需要的区间是连续的区间[L,R],另外就是要求最少的操作次数,每次只能是一个数字,要所有的数字都在区间内,即不断地调整上限和下限,但是需要注意有相同的数字出现。所以不妨逆向思考,我们只要让某一段区间的数达到最大,其他的数直接调整进来,这保证了操作次数最少,最终答案就是总数-区间最大数字的数目。需要提醒,使一个数字增大或者减少并不限制增大多少或者减少多少。
考虑数据范围,为了对每一类数计数,用到了离散化,重新开一个数组记录映射之后得到每一类数字的数量。
为了快速得到某个区间的数字之和,用前缀和提前处理,最后从左到右尺取一遍,不断维护区间最大值。
代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 400005
#define maxm 8
#define inf 2e9
#define ll long long
map<ll,ll>M;
ll num[maxn],cnt;
ll sum[maxn];
int main()
{
ll n,I;
scanf("%lld%lld",&n,&I);
I*=8;
for(ll i=1; i<=n; i++)
{
ll x;
scanf("%lld",&x);
M[x]++;
}
map<ll,ll>::iterator p1=M.begin();
for(;p1!=M.end();p1++)
num[++cnt]=(*p1).second;
if(ceil(log2(cnt))*n<I)
{
printf("0");
return 0;
}
for(int i=1;i<=cnt+1;i++)
sum[i]=sum[i-1]+num[i];
ll ans=0,Max=0;
ll left=1,right=1;
while(right<=cnt)
{
ll len=ceil(log2(right-left+1))*n;
if(len<=I)
right++;
else
{
Max=max(Max,sum[right-1]-sum[left-1]);
left++;
}
}
Max=max(Max,sum[right]-sum[left-1]);
printf("%lld",sum[cnt]-Max);
return 0;
}