题目大意:
给你一个由
2
n
2^n
2n组成的集合,
2
i
2^i
2i有
c
n
t
i
cnt_i
cnti个。
当
i
i
i不为0时,我们可以使用一次操作把
2
i
2^i
2i变成两个
2
i
−
1
2^{i-1}
2i−1。
有2类操作,修改
c
n
t
i
cnt_i
cnti,或者询问至少要多少次操作后有不少于
k
k
k个元素小于等于
2
x
2^x
2x。
n
<
=
30
,
q
<
=
2
e
5
n<=30,q<=2e5
n<=30,q<=2e5
分析:
对于小于等于
2
x
2^x
2x的元素,进行一次操作可以增加一个元素;而对于大于
2
x
2^x
2x的元素,假设为
2
l
2^l
2l,进行
2
l
−
x
−
1
2^{l-x}-1
2l−x−1次操作可以得到
2
l
−
x
2^{l-x}
2l−x个元素。所以我们先考虑把处理大于
2
x
2^x
2x的元素。
假设我们不分解小于等于 2 x 2^x 2x的元素,那么处理出 d d d个 2 x 2^x 2x时,我们按任意顺序分解相同的几个大于 2 x 2^x 2x的元素,需要的操作数时相同的。
那么我们可以从小到大处理大于 2 x 2^x 2x的元素,记录下还需要多少个元素 k k k,以及把所有已选元素分解到 2 0 2^0 20还能产生多少元素 l i m lim lim。因为分解 2 l 2^l 2l可以产生 2 l − x 2^{l-x} 2l−x个元素,如果 2 l − x < = k 2^{l-x}<=k 2l−x<=k,那么直接分解即可;否则先判断 l i m lim lim是否大于等于 k k k,因为不把 2 l 2^l 2l完全分解成 2 x 2^x 2x,那么就没有分解小与等于 2 x 2^x 2x的优。如果 l i m < k lim<k lim<k,考虑用一个操作把它分解成两个 2 l − 1 2^{l-1} 2l−1继续处理即可。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long
const int maxn=35;
using namespace std;
int n,m,op,x;
LL k,lim,ans;
LL a[maxn],bit[maxn];
int main()
{
scanf("%d%d",&n,&m);
for (int i=0;i<n;i++) scanf("%lld",&a[i]);
bit[0]=1;
for (int i=1;i<=n;i++) bit[i]=bit[i-1]*2;
for (int i=1;i<=m;i++)
{
scanf("%d%d%lld",&op,&x,&k);
if (op==1) a[x]=k;
else
{
lim=ans=0;
for (int i=0;i<=x;i++)
{
k-=a[i];
lim+=a[i]*(bit[i]-1);
}
if (k<=0)
{
printf("0\n");
continue;
}
for (int i=x+1;i<n;i++)
{
if (k<a[i]*bit[i-x])
{
int p=k/bit[i-x];
k%=bit[i-x];
lim+=p*(bit[i]-bit[i-x]);
ans+=p*(bit[i-x]-1);
int j=i-1;
for (int j=i;j>=x;j--)
{
if (k<=lim)
{
ans+=k;
k=0;
break;
}
if (bit[j-x-1]<=k)
{
k-=bit[j-x-1];
lim+=bit[j-1]-bit[j-x-1];
ans+=bit[j-x-1];
}
else ans++;
}
break;
}
else
{
k-=a[i]*bit[i-x];
lim+=a[i]*(bit[i]-bit[i-x]);
ans+=a[i]*(bit[i-x]-1);
}
}
if (k<=lim) printf("%lld\n",ans+k);
else printf("-1\n");
}
}
}