bzoj3441

这到题刚刚看的时候没有什么思路,因为想要维护一个动态的过程实在是太困难了。

首先我们将每一个瓶子的高度转化成这个瓶子还能够被升高几次,用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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值