【2019 Wannafly Winter Camp Day5 C】Division 题解

题目大意

  你有一个数列 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 1n105, 1q5105
   1 ≤ a i ≤ 1 0 9 ,   0 ≤ k ≤ 1 0 9 1 \leq a_i \leq 10^9,~0 \leq k \leq 10^9 1ai109, 0k109
  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]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值