这到题刚刚看的时候没有什么思路,因为想要维护一个动态的过程实在是太困难了。
首先我们将每一个瓶子的高度转化成这个瓶子还能够被升高几次,用d[i]表示。
我们想到这一种方法 :
对于当前数列 a[1],a[2],.............,我们设 f[i] 为高度降低了 i 次之后,从a[1]开始到a[n]还能喝水的次数,这就是一个动态规划了。
然而这样以后依然显得十分困难,到底怎么dp呢?
聪明的读者不难想到 :如果当前瓶子 i 在高度降低 k 之后仍然能够被喝到,那么在高度降低 1-(k-1) 的过程中,这个瓶子都是会被喝到的。
注意,我这里说的1-k 并不一定会被答案用到,但是我们需要记录。
那么我们可以从1到n枚举,对于每一个当前的i,我们二分它能够更新到的最大的k[i],再放到线段树里面去验证,怎么验证呢?
我们计算f[k]的值,如果f[k]>d[i]-k,那么当前的k就不合法,直到我们二分到终点,然后在线段树1-k之间打上一个加一标记,
以后询问的时候就可以用到了(f[k]就是在线段树中询问到的,注意k可以等于0)。
然后呢?我们设当前一个变量now=0(因为一开始所有瓶子肯定都是没喝过的,就是0),我们在线段树中计算f[now]的值,
如果f[now]=0,那么就直接可以退出了,否则Ans+=f[now],now+=f[now],继续询问;
以上的做法显然是 n*logn*logn 的,会Tle。
还有什么优化方法呢?
主要的时间就在这一个二分上,没有办法很快的确定当前点到底能够更新到哪个k。
我们这样子考虑,先就把k赋值成d[i],如果d[i]-k<f[k],那么我们就将k减去abs(f[k]-d[i]+k),这样子就可以更加靠近我们需要的那个k了。
于是把二分通过这样的方式去掉,复杂度就变成了 n*log*(??) ,虽然不知道这样子的复杂度到底是多少,但是反正是很快了。
贴一份代码 :
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<string>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#define inf 2000000010
using namespace std;
int n,m,H,W[100010],A[100010],D[100010],Ans=0;
int getint() {
int v=0,p=1;
char c=0;
while(c<'0'||c>'9') { if(c=='-') p=-1; c=getchar(); }
while(c>='0' && c<='9') { v=(v<<3)+(v<<1)+c-'0'; c=getchar(); }
return v*p;
}
struct Segment_Tree {
int val[6000010],Lson[6000010],Rson[6000010],tot;
Segment_Tree () { tot=1; }
int Ask(int Now,unsigned int L,unsigned int R,int Num) {
if(L==R) return val[Now];
unsigned int Mid=(L+R)>>1;
if(Num>Mid) return Ask(Rson[Now],Mid+1,R,Num);
else return val[Rson[Now]]+Ask(Lson[Now],L,Mid,Num);
}
void Build(int Now,unsigned int L,unsigned int R,int Num) {
if(L==R) { val[Now]++; return ;}
unsigned int Mid=(L+R)>>1;
if(Num<=Mid)
{
if(Lson[Now]==0) Lson[Now]=++tot;
Build(Lson[Now],L,Mid,Num);
}
else
{
if(Rson[Now]==0) Rson[Now]=++tot;
Build(Rson[Now],Mid+1,R,Num);
}
val[Now]=val[Lson[Now]]+val[Rson[Now]];
}
}; Segment_Tree M;
int main()
{
freopen("lx.in","r",stdin);
freopen("lx.out","w",stdout);
n=getint();
m=getint();
H=getint();
for(int i=1;i<=n;i++) W[i]=getint();
for(int i=1;i<=n;i++) A[i]=getint();
for(int i=1;i<=n;i++)
{
if(W[i]>H) D[i]=0;
else D[i]=(H-W[i])/A[i]+1;
}
for(int i=1;i<=n;i++)
{
int K=D[i],V=M.Ask(1,0,inf,K);
while(D[i]<=K+V&&K>=0)
{
int F=abs(D[i]-K-V)+1;
K-=F;
V=M.Ask(1,0,inf,K);
}
if(K>=0) M.Build(1,0,inf,K);
}
int X=0;
for(int i=1;i<=m;i++)
{
int L=M.Ask(1,0,inf,X);
Ans+=L,X+=L;
}
printf("%d",Ans);
return 0;
}