把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ2667】[CQOI2012] 模拟工厂(暴力枚举+贪心验证)

点此看题面

大致题意: 你有一个工厂,初始生产力\(p=1\),每个单位时间你可以选择令\(p\)\(1\)或生产\(p\)个商品。有\(k\)个任务,对于第\(i\)个任务你可以在第\(t_i\)个单位时刻减少\(g_i\)个商品,获得\(v_i\)元收入。

为什么这篇博客没有前言?因为这整篇博客都是前言。

\(n\)这么小怎能不想到暴枚

一开始没看数据范围习惯性以为\(n\le10^5\)还思考了挺久的说。。。

结果鼠标往下一滑发现\(n\le15\) emmm。。。

我想应该很容易就能想到暴力枚举\(2^{15}\)种情况,规定完成哪些任务,然后对于每种情况再进行验证。

一时兴奋想出来又证伪掉的解方程

考虑已知要选择完成的任务,如何判断是否可以完成。

我一开始有个非常\(naive\)的想法,就是在每两个任务之间,先尽可能提高生产力,然后最后再死亡冲刺完成任务。

我令\(p\)表示初始生产力,\(k\)表示两个任务间的总时间,\(s\)为还需生产的商品(需要生产的商品减去上次剩余的商品)。

然后设\(x\)表示提高生产力的时间,于是就得到一个方程:

\[(p+x)(k-x)>s\Rightarrow x^2+(p-k)x-pk+s<0 \]

显然这是一个一元二次不等式,其中\(a=1,b=p-k,c=-pk+s\)

\(\triangle=b^2-4c<0\),显然无解。

否则,贪心地想,令\(x\)取最大值(为什么要让生产力尽量大?因为社会老师讲过,看到任何问题都要想到,生产力才是根本原因),得到\(x=\frac{-b+\sqrt{b^2-4c}}2\)

这上面的过程看似没有问题,然而,我后来猛然惊觉,你不一定要疯狂提升生产力,实际上可以预先生产商品为下一个任务做准备。

于是正解在我的脑海中一闪而过,可惜不够自信的我没能将其抓住。

最后又想了半天没有结果,无可奈何点开题解发现清一色解方程,真的就差一步啊。。。

在原先基础上稍作修改就能得到的正解

正如我证伪原先做法时所想的那样,与其疯狂提升生产力,实际上可以预先生产商品为之后的任务做准备。

那时候我就突然想到:为什么不对之后所有任务解一次方程,然后统计\(min\{x_{max}\}\)作为提升的生产力呢?

然而,当时因为刚刚\(Hack\)掉自己,所以武断认为这个想法看看都不对劲。

最终,又一次迎来打脸,看来我和正解最终也就只差这一步之遥啊!

得出结论:我太菜了。

(上面的过程更多地是在体现我的做题经过,因此思路可能有点杂乱,具体实现详见代码。)

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 15
#define LL long long
using namespace std;
int n,T;
struct data
{
	int t,g,v;I data(CI a=0,CI b=0,CI c=0):t(a),g(b),v(c){}
	I bool operator < (Con data& o) Con {return t<o.t;}
}s[N+5],S[N+5];
I bool Check()
{
	RI i,j,p=1,k,x,t;LL s,w=0,b,c;for(i=1;i<=T;++i)//枚举当前任务
	{
		for(t=S[i].t-S[i-1].t,s=-w,j=i;j<=T;++j)//t存储最多能用多少时间发展生产力,s记录需要生产的商品总和
		{
			if(k=S[j].t-S[i-1].t,(s+=S[j].g)<=0) continue;//若之前剩余的商品已经能完成任务,跳过
			if(b=p-k,c=-1LL*p*k+s,b*b-4*c<0) return 0;//若△<0,说明无解,返回false
			x=floor((-1.0*b+sqrt(b*b-4*c))/2),t>x&&(t=x);//解方程,t统计x最大值的最小值
		}w+=(p+=t)*(S[i].t-S[i-1].t-t)-S[i].g;//更新生产力,更新剩余商品
	}return 1;
}
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%d",&s[i].t,&s[i].g,&s[i].v);
	RI j,l=1<<n,res,ans=0;for(sort(s+1,s+n+1),i=0;i^l;++i)//枚举完成哪些任务状压下的状态
	{
		for(T=res=0,j=1;j<=n;++j) (i>>j-1)&1&&(res+=(S[++T]=s[j]).v);//记下需要完成的任务及总收益
		res>ans&&Check()&&(ans=res);//若总收益大于当前答案,再去验证答案(一个没啥软用的小剪枝)
	}return printf("%d",ans),0;
}
posted @ 2020-05-18 16:06  TheLostWeak  阅读(127)  评论(0编辑  收藏  举报