DESCRIPTION
小 L 有一串 Q 个数排成一排,从左往右是 A1 开始一直到 AQ,它一直很喜欢这串数。
有一天,小 L 去高考报名了,小 J 和小 N 过来用这串数玩游戏。
一开始,小 N 手上有一个空序列,定义变量C:=0。小 J 从左往右依次把序列中的数取出,放到小 N 手上序列的最右边,如果放完之后,整个序列的混乱度超过了 M,小 N 就会把手上的所有数全部扔掉,然后令 C:=C+1。
定义一个长度为 K 序列的混乱度 S=K∑i=1Bi×Vi,其中 Bi 表示序列中第 i 小的数,V 为一个给定的序列。
小 J 和小 N 想知道,加入每个数之后,C 是多少。
INPUT
第一行两个整数
Q,
M接下来一行
Q个整数, 第
i个整数表示
Ai接下来一行
Q个整数, 第
i个整数表示
Vi
OUTPUT
一行
Q个整数, 第
i个整数表示加入第
i个数之后的
C, 相邻两个整数之间用空格隔开, 注意最后一个数后不要输出空格输出完请换行
SAMPLE INPUT
5 11 3 2 5 41 1 1 1 1
SAMPLE OUTPUT
0 1 2 3 4
HINT
1≤Q≤300000,0≤M≤1018,1≤Ai≤108,1≤Vi≤104对于所有的
1≤i≤Q−1,Vi≤Vi+1
B
一个显然的想法是每一次二分加入到第几个数的时候, 混乱度超过M, 然后暴力检验, 复杂度显然可以是O(N2logN)级别
我们可以换一种二分方式
令当前左端点为L, 我们先找到一个最小的K, 使得[L,L+2K)这个区间混乱度超过M
然后右端点在[L+2K−1,L+2K)中二分即可
考虑每删去至少2K−1个数, 所需要的时间复杂度最大为O(2KKlogN)
故总复杂度为O(Nlog2N)
当初想的时候就觉得如果M大,C增加的就少,中间算的就不多,如果M小,排序的数字就小。怎么都想不明白,双二分。很经典。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=300000;
int ans[300005];
ll a[300005];
ll v[300005];
ll tmp[300005];
int p[29];
void init()
{
p[0]=1;
for(int i=1; i<=25; i++)
p[i]=p[i-1]*2;
}
int judge(int l,int r,ll m)
{
ll sum=0;
int cnt=0;
for(int i=l; i<=r; i++)
tmp[cnt++]=a[i];
sort(tmp,tmp+cnt);
for(int i=0;i<cnt;i++)
sum+=tmp[i]*v[i];
if(sum>m)
return 1;
return 0;
}
int g(int l,int r1,int r2,ll m)
{
while(r1<r2)
{
int midr=(r1+r2)>>1;
if(judge(l,midr,m))
r2=midr;
else
r1=midr+1;
}
return r1;
}
int check(int l,int n,ll m)
{
int r1=l;
int r2=l;
for(int i=0;; i++,r2+=p[i])
{
if(r2>n)
r2=n;
if(judge(l,r2,m))
break;
r1=r2+1;
if(r2==n)
break;
}
return g(l,r1,r2,m);
}
void f(int n,ll m)
{
int i=0;
while(i<n)
{
int j=check(i,n,m);
ans[j]=ans[i==0?0:i-1]+1;
i=j+1;
}
for(i=1; i<n; i++)
{
ans[i]=max(ans[i-1],ans[i]);
}
}
int main()
{
int n;
ll m;
init();
while(~scanf("%d%lld",&n,&m))
{
for(int i=0; i<n; i++)
scanf("%lld",&a[i]);
for(int i=0; i<n; i++)
scanf("%lld",&v[i]);
memset(ans,0,sizeof(ans));
f(n,m);
for(int i=0; i<n; i++)
printf("%d%c",ans[i],i==n-1?'\n':' ');
}
return 0;
}