题目大意
你有一个数列
a
1
,
a
2
,
⋯
 
,
a
n
a_1,a_2,\cdots,a_n
a1,a2,⋯,an。你可以进行这样的一次操作,每次选择数列中其中一个数然后将其除
2
2
2 下取整,也就是选择一个数
a
i
a_i
ai,变成
⌊
a
i
2
⌋
\lfloor \frac{a_i}{2} \rfloor
⌊2ai⌋。
一共有
q
q
q 个询问,每次你考虑数列中
[
l
,
r
]
[l,r]
[l,r] 这段数,即
a
l
,
a
l
+
1
,
a
l
+
2
,
⋯
 
,
a
r
a_l,a_{l+1},a_{l+2},\cdots,a_r
al,al+1,al+2,⋯,ar,对这些数字进行不超过
k
k
k 次操作,这些数字的总和最小值可能是多少。
1
≤
n
≤
1
0
5
,
1
≤
q
≤
5
∗
1
0
5
1 \leq n \leq 10^5,~1 \leq q \leq 5*10^5
1≤n≤105, 1≤q≤5∗105
1
≤
a
i
≤
1
0
9
,
0
≤
k
≤
1
0
9
1 \leq a_i \leq 10^9,~0 \leq k \leq 10^9
1≤ai≤109, 0≤k≤109
5000 ms,256 MB
\\
\\
\\
题解
首先一个数字最多除
log
\log
log 次,
log
\log
log 次以后就变成
0
0
0 了。
然后
a
i
a_i
ai 每除一次
2
2
2,就相当于减去一个
⌈
a
i
2
⌉
\lceil \frac{a_i}{2} \rceil
⌈2ai⌉。这样的话就可以把每个
a
i
a_i
ai 拆成
log
\log
log 个数字之和。
那么就相当于有
n
n
n 个物品,每个物品可以拆成
log
\log
log 个小物品,每个小物品有一个收益。然后每次询问一个区间,取
k
k
k 个小物品,能获得的最大收益是多少。
没啥特殊情况的话那就是区间前 k k k 大和,主席树一下就行了。
然后发现空间只有 256 MB
各凭本事卡内存
——杜老师
题解的卡内存的姿势还是有丶巧妙的。
题解的姿势就是,把这个主席树拆开来,拆成若干个小一点的主席树。
怎么拆呢?就是把小物品按其价值的二进制位数分层,分成
log
\log
log 层,每个大物品在每一层只会有一个小物品(每除一次有一个)。这样的话,每一层就只有
n
n
n 个物品了,那么每一层的主席树就只有
O
(
n
log
a
)
O(n \log a)
O(nloga) 那么大了。
然后求区间前
k
k
k 大和,倒着枚举层(从高位到低位),每个询问只会在某一层处跑线段树二分,因此时间也还是
O
(
q
log
a
)
O(q \log a)
O(qloga) 的。
代码
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long LL;
const int maxn=1e5+5, maxq=5e5+5, MX=31;
int n,q,maxw,maxx,l[maxq],r[maxq],c[maxq];
LL s[maxn];
vector<pair<int,int>> v[MX+5];
int root[maxn],tot,son[maxn*40][2],num[maxn*40];
LL sum[maxn*40];
int New()
{
tot++;
num[tot]=sum[tot]=son[tot][0]=son[tot][1]=0;
return tot;
}
int tr_xg(int k,int last,int l,int r,int x)
{
while (l<r)
{
sum[k]=sum[last]+x, num[k]=num[last]+1;
int t1=(l+r)>>1;
if (x<=t1)
{
son[k][0]=New();
son[k][1]=son[last][1];
k=son[k][0], last=son[last][0], r=t1;
} else
{
son[k][0]=son[last][0];
son[k][1]=New();
k=son[k][1], last=son[last][1], l=t1+1;
}
}
sum[k]=sum[last]+x, num[k]=num[last]+1;
}
LL tr_cx(int k,int last,int l,int r,int x)
{
LL re=0;
while (l<r)
{
int t1=(l+r)>>1;
if (num[son[k][1]]-num[son[last][1]]>=x) k=son[k][1], last=son[last][1], l=t1+1;
else
{
re+=sum[son[k][1]]-sum[son[last][1]];
x-=num[son[k][1]]-num[son[last][1]];
k=son[k][0], last=son[last][0], r=t1;
}
}
return (x) ?re+(sum[k]-sum[last])/(num[k]-num[last])*x :re ;
}
LL ans[maxq];
int main()
{
scanf("%d %d",&n,&q);
fo(i,1,n)
{
scanf("%lld",&s[i]);
maxx=(s[i]>maxx) ?s[i] :maxx ;
int w=0;
for(int x=s[i]; x; x>>=1) w++;
maxw=max(maxw,w-1);
for(int cnt=w-1, x=s[i]; x; cnt--, x>>=1) v[cnt].push_back(make_pair(i,x-(x>>1)));
s[i]+=s[i-1];
}
fo(i,1,q)
{
scanf("%d %d %d",&l[i],&r[i],&c[i]);
ans[i]=s[r[i]]-s[l[i]-1];
}
fd(i,maxw,0)
{
tot=-1;
int last=New();
memset(root,0,sizeof(root));
for(auto p:v[i])
{
tr_xg(root[p.first]=New(),last,1,maxx,p.second);
last=root[p.first];
}
fo(i,1,n) if (!root[i]) root[i]=root[i-1];
fo(j,1,q) if (c[j])
{
int cnt=num[root[r[j]]]-num[root[l[j]-1]];
if (cnt>=c[j])
{
ans[j]-=tr_cx(root[r[j]],root[l[j]-1],1,maxx,c[j]);
c[j]=0;
} else
{
c[j]-=cnt;
ans[j]-=sum[root[r[j]]]-sum[root[l[j]-1]];
}
}
}
fo(i,1,q) printf("%lld\n",ans[i]);
}