玲珑杯-1112 (倍增+二分)

DESCRIPTION

小 L 有一串 QQ 个数排成一排,从左往右是 A1A1 开始一直到 AQAQ,它一直很喜欢这串数。

有一天,小 L 去高考报名了,小 J 和小 N 过来用这串数玩游戏。

一开始,小 N 手上有一个空序列,定义变量C:=0C:=0。小 J 从左往右依次把序列中的数取出,放到小 N 手上序列的最右边,如果放完之后,整个序列的混乱度超过了 MM,小 N 就会把手上的所有数全部扔掉,然后令 C:=C+1C:=C+1

定义一个长度为 KK 序列的混乱度 S=Ki=1Bi×ViS=∑i=1KBi×Vi,其中 BiBi 表示序列中第 ii 小的数,VV 为一个给定的序列。

小 J 和小 N 想知道,加入每个数之后,CC 是多少。

INPUT
第一行两个整数 QQ MM接下来一行 QQ个整数, 第 ii个整数表示 AiAi接下来一行 QQ个整数, 第 ii个整数表示 ViVi
OUTPUT
一行 QQ个整数, 第 ii个整数表示加入第 ii个数之后的 CC, 相邻两个整数之间用空格隔开, 注意最后一个数后不要输出空格输出完请换行
SAMPLE INPUT
5 11 3 2 5 41 1 1 1 1
SAMPLE OUTPUT
0 1 2 3 4
HINT
1Q300000,0M1018,1Ai108,1Vi1041≤Q≤300000,0≤M≤1018,1≤Ai≤108,1≤Vi≤104对于所有的 1iQ1,ViVi+11≤i≤Q−1,Vi≤Vi+1
SOLUTION
题解:倍增+二分
先说说自己在做这个题的时候卡在哪了吧。我想应该大部分就是卡在这么处理在添加的时候怎么确定第k小的数。想到这肯定去想用排序,但是排序又肯定会超时。
那么现在就来通过这个题来了解一下倍增二分的奥妙。那么根据这个题的题意,每次加一个数其实他的混乱度都是在增加的,然而添加又都是从左边开始一个一个的添加的。那么可想肯定有一个右边界使得在这个区域内的值得混乱度超过m。现在问题转化为了怎么确定右边界,当然最简单的办法就是一个一个的取寻找右边界,这个和上面讲个超时的做法是一样的,毫无改进。那么既然用到了查找寻值,肯定能想到用二分去查找呀。那么既然是二分就要用到求右边界的L和R,如果只是从左边界到n来二分排序的也会超时的。
现在问题就转化为了怎么来快速的确定一个右边界的大概区间然后用二分判断呢。这里就是用倍增的地方了。用倍增找一个混乱度大于m的r2边界,在找r2的时候,要记得更新r1边界。具体代码中也有解释了。
#include <iostream>
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

int pow1[30];
ll a[300300],v[300300],temp[300300];
int ans[300300];
void init()
{
    pow1[0]=1;
    for(int i=1;i<25;i++)
    {
        pow1[i]=pow1[i-1]*2;
    }
}

bool judge(int l,int r,ll m)
{
    int i,j;
    for( i=0,j=l;j<=r;j++)
    {
        temp[i++]=a[j];
    }
    sort(temp,temp+i);///排序求这个区间的K值,看是否大于m
    ll sum=0;
    for(int j=0;j<i;j++)
    {
        sum+=temp[j]*v[j];
    }
    return sum>m;
}
int erfen(int r1,int r2,int l,ll m)
{
    while(r1<r2)
    {
        int mid=(r1+r2)/2;
        if(judge(l,mid,m))
        {
            r2=mid;
        }
        else
        {
            r1=mid+1;
        }
    }
    return r1;///确切的右端点
}
int cheak(int l,int n,ll m)
{
    int r1=l,r2=l;///寻找大于m的右端点一定在区间r1-r2内
    for(int i=0;;r2+=pow1[i],i++)///倍增法求大概区间
    {
        if(r2>n)r2=n;
        if(judge(l,r2,m))break;
        r1=r2+1;///注意r1的伟大作用
        if(r2==n)break;
    }
    return erfen(r1,r2,l,m);///然后利用二分在r1-r2范围内找出确切的第一个大于m的位置
}
void work(int n,ll m)
{
    for(int i=0;i<n;)///左端点
    {
        int j=cheak(i,n,m);///右端点
        ans[j]=ans[i==0?i:i-1]+1;///大于M的断点
        i=j+1;///左端点的更新
    }
    for(int i=1;i<n;i++)
    {
        ans[i]=max(ans[i],ans[i-1]);///求出所有的点的结果
    }
    return ;
}
int main()
{
    init();///初始化倍增值
    int n;
    ll m;
    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));///结果的存储

    work(n,m);

    for(int i=0;i<n;i++)
    {
        if(i==0)
        printf("%d",ans[i]);
        else printf(" %d",ans[i]);
    }
    printf("\n");
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值