[wxk.ink] 1002: 你真的会贪心吗?(splay优化dp)

题目描述

walht由于天天翘课,所以会在期末考前疯狂补作业。有一门课的老师布置了k份作业,但是walht根本不想要高绩点,他只要做完其中的m份作业就可以及格了。可是由于walht太懒了,直到离结束还有n分钟的时候他才开始写作业。现在walht按循序告诉你k份作业的所需要的时间,walht由于太匆忙了,只会贪心的做题,也就是说如果他能在剩余的时间内能完成当前的作业,他就会去写,否则就会写下一题,注意当剩余时间为0的时候,walht就不行写任何的题了。 
walht实在是太懒了,他想要问你,他还能休息多久,即n最小为多少的时候他要开始写题,在保证及格的前提下。 

输入

首先有一个数字T,表示有T组数据。  

每组数据:  

第一行两个数字k,m。(1 <= m <= k <= 2 * 105)  

第二行k个数字表示每到题所需的时间a1……ak。(0 <= ai <= 2 * 105)  

数据保证sum_k <= 2 * 105。  

输出

每组输出一个数字n,占一行。 

样例输入

5 4
1 2 3 4 5
5 5
1 2 2 3 3

样例输出

10
11

题目链接:http://wxk.ink/problem.php?id=1002  

此题的原题其实是牛客上鸡尾酒买罐子这题的加强版,原题题解说是暴力从小到大枚举答案的方式。

但是我们可以真·贪心做,从后往前枚举每个val[i],维护一个dp[2][j]滚动数组(不要在意数组的名字。。),j表示从i编号的罐子开始往后买罐子买j个罐子的最小花费,然后遍历val值时每次只需更新比val[i]大于等于的dp[id][j]的值,因为小于val[i]的话,当从i开始买罐子,是不会去买i编号的罐子的,因为钱不够,大于等于的情况就显然了,然后注意要特判val的值等于0的情况,当j=1时,即买一个罐子,dp[id][1]的钱就可以定为1了(必须剩1块,不然直接就退出不买了),然后对j>1的情况,就和前面的一样,状态转移为dp[id][j]=dp[id^1][j-1]+val[i]。。。但是这样的复杂度是O(n*m)大概。。。

这题加强版显然就不够用了。。。

然后我们观察dp数组的定义和值发现,dp是个非下降的数列,而且可以开成一维就够了dp[j]。

所以我的思路是二分第一个大于等于val[i]的位置pos,在pos-1~pos间插入一个dp[pos-1]的值,删掉最后一个因为区间向右移动了一位多出来的dp[n+1],然后再给区间[pos,n]整体加上val[i]。分块暴力n*sqrt(n)复杂度还是爆炸,不行。然后我是用splay平衡树做的,为了取区间方便,对相同dp值不合并在一个点上。复杂度O(n*logn+...),具体看代码好了。。。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
#define FI first
#define SE second
#define MP make_pair
#define PI pair<int,int>
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define test printf("here!!!\n")
using namespace std;
const int mx=2e5+10;
const int MAX=1e6+10;
int n,m;
ll a[mx];
ll upc[MAX],val[MAX];
int ch[MAX][2],sz[MAX],par[MAX],tot,root;
void pushup(int x)
{
    int l=ch[x][0],r=ch[x][1];
    sz[x]=1;
    if (l) sz[x]+=sz[l];
    if (r) sz[x]+=sz[r];
}
void pushdown(int x)
{
    if (upc[x])
    {
        if (val[x]==LINF) val[x]=upc[x];
        else val[x]+=upc[x];
        int y=ch[x][0],z=ch[x][1];
        if (y)
        {
            upc[y]+=upc[x];
        }
        if (z)
        {
            upc[z]+=upc[x];
        }
        upc[x]=0;
    }
}
/*void ac(int x)
{
    //pushdown(x);
    if (ch[x][0]) ac(ch[x][0]);
    if (val[x]==LINF) printf("LINF ");
    else printf("%lld ",val[x]);
    if (ch[x][1]) ac(ch[x][1]);
}
void out()
{
    printf("updata:\n");
    ac(root);
    printf("\n");
}*/
int get(int x)
{
    return ch[par[x]][1]==x;
}
void rot(int x)
{
    int y=par[x],z=par[y],k=get(x),w=get(y);
    ch[z][w]=x;
    par[x]=z;
    ch[y][k]=ch[x][k^1];
    par[ch[x][k^1]]=y;
    ch[x][k^1]=y;
    par[y]=x;
    pushup(y),pushup(x);
}
void splay(int x,int goal)
{
    while (par[x]!=goal)
    {
        int y=par[x],z=par[y];
        if (z!=goal)
        {
            get(x)==get(y)?rot(y):rot(x);
        }
        rot(x);
    }
    if (!goal) root=x;
}
int build(int l,int r,int fa)//建平衡树
{
    if (l>r) return 0;
    int mid=(l+r)>>1,now=++tot;
    mid==1?val[now]=0:val[now]=LINF;
    par[now]=fa;
    upc[now]=0;
    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    sz[now]=1;
    pushup(now);
    return now;
}
int find(ll x)//返回小于x的个数,+1即为大于等于x的第一个编号
{
    int r=root;
    int hgf=0;
    while (r)
    {
        pushdown(r);
        if (val[r]<x) hgf+=sz[ch[r][0]]+1,r=ch[r][1];
        else r=ch[r][0];//一直找到最后一个小于x的数
    }
    return hgf;
}
int kth(int k)//返回排名为k的数的下标
{
    if (sz[root]<k) return 0;
    int r=root;
    while (1)
    {
        pushdown(r);
        int y=ch[r][0];
        if (sz[y]+1<k)
        {
            k-=sz[y]+1;
            r=ch[r][1];
        }
        else
        {
            if (sz[y]<k) return r;
            else r=y;
        }
    }
}
void ins(int pos)//在pos位后插入一位
{
    int x=kth(pos),y=kth(pos+1);
    splay(x,0),splay(y,x);
    int now=++tot;
    ch[y][0]=now;
    par[now]=y;
    val[now]=val[x];
    upc[now]=0;
    ch[now][0]=ch[now][1]=0;
    sz[now]=1;//注意这个,因为没有pushup(now)的,所以需要写一个这个
    pushup(y),pushup(x);
}
void del(int pos)//删除pos位
{
    int x=kth(pos-1),y=kth(pos+1);
    splay(x,0),splay(y,x);
    ch[y][0]=0;
    pushup(y),pushup(x);
}
void updata(int l,int r,ll c)//对区间[l,r]加上c
{
    ins(l-1);
    del(r+1);
    int x=kth(l-1),y=kth(r+1);
    splay(x,0),splay(y,x);
    int z=ch[y][0];
    upc[z]+=c;
    pushdown(z);
}
int main()
{
    int t;
    scanf("%d",&t);
    while (t--)
    {
        scanf("%d%d",&n,&m);
        tot=0;//初始化直接tot等于0就好了
        a[1]=0;//这个其实用不到,build的时候没有用这个a数组。。。2333
        for (int i=2;i<=n+1;++i)
        {
            scanf("%lld",&a[i]);
        }
        a[n+2]=LINF;//这个也是用不到
        root=build(1,n+2,0);//build时加入左右2个哨兵,方便点
        ++m;//区间向右移动了一位
        for (int i=n+1;i>=2;--i)
        {
            int bg=max(2,m-i+2);//这个的意思是比如还剩下1个还没遍历,那对于做m个作业的,就只可能从做m-1个作业上更新来了,剪枝
            int endd=min(m,n-i+3);//同理
            if (a[i]==0)
            {
                if (bg==2)//对于bg能碰到2的,更新做1个作业的
                {
                    if (bg+1<=endd)
                    {
                        updata(bg+1,endd,a[i]);//大于1个的同理更新
                    }
                    int tp=kth(2);
                    val[tp]=1;//更新做1个作业的
                    upc[tp]=0;//貌似多余
                }
                else updata(bg,endd,a[i]);//碰不到的就照常更新
            }
            else
            {
                int rk=find(a[i]);
                if (rk+1<=endd) updata(rk+1,endd,a[i]);
                //这里其实应该还可以有一个max(rk+1,bg)的操作,忘记了。。
            }
        }
        printf("%lld\n",val[kth(m)]);
    }
}

这题学长貌似是用线段树做的(可能不是我上面这个思路,所以还是我太菜了,%%%)。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值