2019.01.29【NOIP提高组】模拟B组 JZOJ 4245 er

版权声明:虽然本蒟蒻很菜,但各位dalao转载请注明出处谢谢。 https://blog.csdn.net/xuxiayang/article/details/86689816

D e s c r i p t i o n Description

n 2 n\leq 2 件装备,有 m m 个升级卡,分为赋值、增值、乘值三种,现要从中选择 k k 个升级卡,使得装备的战斗力之和最大

对于20% 的数据, N = 1 N = 1
对于全部数据 M , K 100 ; N 2 M,K ≤ 100;N ≤ 2 ,最多一张赋值卡。
输入数据中所有数不超过2000。


S o l u t i o n Solution

贪心好题
对于 n = 1 n=1 的情况,直接分成使用赋值卡和不使用赋值卡的方式
对于一些增值和乘值卡,显然是先使用增值再乘值更优,所以我们可以暴力给那件装备增值的值,然后再用乘值卡乘,最后计算即可。加上前缀和优化后时间复杂度: O ( m ) O(m)

对于 n = 2 n=2 的情况,分成给1使用赋值卡,给2使用赋值卡和不使用赋值卡三种
这时因为最后的答案是所有装备值的乘积,所以乘值卡给谁并不重要,主要是增值的分配问题。
注意到我们有 m m 张卡,从中选出任意张总和不超过 s j f [ i ] sjf[i] 的卡,求方案数
这其实就是0/1背包问题
因此,我们用0/1背包求出能否用现在的增值卡拼凑出这个值,既然能算出分配给1的值,则用总值减去分配给1的值即为分配给2的值,这时我们就可以 O ( 1 ) O(1) 转移啦,加上前缀和优化后复杂度为 O ( m a ) O(m\sum a)


C o d e Code

#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;int n,m,k,a,b,fz,jf[101],cf[101],ljf,lcf,opt,c,nowa,nowb,f[2000001];
long long sjf[101];
double now,ans,scf[101];
inline char Getchar()
{
    static char buf[10000000],*p1=buf+10000000,*pend=buf+10000000;
    if(p1==pend)
    {
        p1=buf; pend=buf+fread(buf,1,10000000,stdin);
        if (pend==p1) return -1;
    }
    return *p1++;
}
inline long long read()
{
    char c;int d=1;long long f=0;
    while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
    while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
    return d*f;
}
inline bool cmp(int x,int y){return x>y;}
signed main()
{
	freopen("1.txt","r",stdin);
	n=read();m=read();k=read();
	a=read();
	if(n==1)//分类讨论
	{
		for(register int i=1;i<=m;i++)
		{
			opt=read();c=read();
			if(opt==1) fz=c;
			if(opt==2) jf[++ljf]=c;
			if(opt==3) cf[++lcf]=c;
		}
		sort(jf+1,jf+1+ljf,cmp);
		sort(cf+1,cf+1+lcf,cmp);//肯定是越大越优了啦
		for(register int i=1;i<=ljf;i++) sjf[i]=sjf[i-1]+jf[i];
		scf[1]=log(cf[1]);
		for(register int i=2;i<=lcf;i++) scf[i]=scf[i-1]+log(cf[i]);//前缀和计算,用到一个公式log(ab)=log(a)+log(b)
		if(fz>a)//此时赋值才可能是更优的,否则就是浪费
		{
			now=0;
			for(register int i=0;i<min(ljf,k);i++)
			{
				nowa=fz+sjf[i];
				now=max(now,log(nowa)+scf[min(k-1-i,lcf)]);//前面的加,后面的乘
			}
			ans=now;
		}
		now=0;
		for(register int i=0;i<=min(ljf,k);i++)//注意循环的上界不一样,因为我上面已经使用了一张卡所以是小于,而这里还没用所以是不大于
		{
			nowa=a+sjf[i];
			now=max(now,log(nowa)+scf[min(k-i,lcf)]);//同样的因为这里没有多用一张卡所以不用再-1
		}
		ans=max(ans,now);
		printf("%.3lf\n",ans);
	}
	else
	{
		b=read();
		for(register int i=1;i<=m;i++)
		{
			opt=read();c=read();
			if(opt==1) fz=c;
			if(opt==2) jf[++ljf]=c;
			if(opt==3) cf[++lcf]=c;
		}
		sort(jf+1,jf+1+ljf,cmp);
		sort(cf+1,cf+1+lcf,cmp);
		for(register int i=1;i<=ljf;i++) sjf[i]=sjf[i-1]+jf[i];
		scf[1]=log(cf[1]);
		for(register int i=2;i<=lcf;i++) scf[i]=scf[i-1]+log(cf[i]);//以上部分和上面一样
		if(fz)
		{
			memset(f,0,sizeof(f));
			f[0]=1;
			for(register int i=0;i<min(ljf,k);i++)
			{
				for(register int j=sjf[ljf];j>=jf[i];j--) f[j]|=f[j-jf[i]];//背包计算当前方案是否合法
				for(register int j=0;j<=sjf[i];j++)
				if(f[j])//如果合法
				{
					nowa=fz+j;nowb=b+sjf[i]-j;//j点分给a,其它的分给b
					now=max(now,log(nowa)+log(nowb)+scf[min(k-i-1,lcf)]);//因为用了赋值卡,所以要-1
					nowa=a+j;nowb=fz+sjf[i]-j;//反之
					now=max(now,log(nowa)+log(nowb)+scf[min(k-i-1,lcf)]);
				}
			}
			ans=max(ans,now);
		}
		memset(f,0,sizeof(f));
		f[0]=1;
		for(register int i=0;i<=min(ljf,k);i++)
		{
			for(register int j=sjf[ljf];j>=jf[i];j--) f[j]|=f[j-jf[i]];
			for(register int j=0;j<=sjf[i];j++)//同理
			if(f[j])
			{
				nowa=a+j;nowb=b+sjf[i]-j;
				now=max(now,log(nowa)+log(nowb)+scf[min(k-i,lcf)]);//因为没用赋值卡,所以不用减一
			}
		}
		ans=max(ans,now);
		printf("%0.3lf",ans);
	}
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/86689816