题目描述
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)]);
}
}
这题学长貌似是用线段树做的(可能不是我上面这个思路,所以还是我太菜了,%%%)。。