[置顶] 勿忘把春夏之交vijos的比赛再看看

题1:

小雪与小可可正在玩一种数字游戏。他们准备了n卡片,每一张卡片上都有一个整数。游戏开始后,小雪会先选择一个不小于a且不大于b的整数t,并告诉小可可这个数字t是多少。之后小可可会挑出恰好k张卡片,并将这k张卡片上的数字相加,得到的和数记为m。

小雪希望t和m差的绝对值尽可能大,而小可可却希望t和m差的绝对值尽可能小。在游戏开始前,他们二人都知道n,a,b和k是多少,也知道每一张卡片上的数字是多少。在小雪决定了t的大小后,不能再修改,之后才由小可可挑选纸牌。

小雪希望知道,在二人都尝试最优策略的情况下,t和m差的绝对值最大可以有多大?

题解:

为了找出t和m的最大差,我们需要先找出所有可能的m,也就是要算出来有哪些数字可以通过在n个数字中挑选k个来得到。这一个简化版的01背包问题,记F[i][j][x]表示前i个数字中选出j个来,是否可以组成数字x。这样做时间复杂度是O(nkMAXB)的,可以通过85%的数据。
  又可以发现F[i][j][x]全都是boolean型的,考虑把多个F[i][j][x]在最后一维进行压缩,例如我们可以用一个64位整数来表示64个boolean值。01背包的所有转移都可以用位运算来实现。这便可以通过100%的数据。

这样的背包dp真是没见过,应该是我见识太少了QAQ;表示从前i个数中选出k个,是否可以组成的数x,以前的f【i】【x】是从前i个数中随便选,不管选几个,问是否可以选出和为x的. 头上这种稍微麻烦一点(不过好歹明白了)

不是满分算法,满分算法还是要加位运算,一般只有boolean的背包动规,是可以用位运算的

#include<algorithm>
#include<string>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>

using namespace std;

int f[69][69][19500],n,k,a,b,x[259];
int main()
{
	scanf("%d%d%d%d",&n,&k,&a,&b);
	for (int i=1;i<=n;i++) scanf("%d",&x[i]);
	
	f[0][0][0]=1;//边界,前0个数选0个组成0是可以的
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=min(k,i);j++)
		{
		<span style="white-space:pre">	</span>for (int l=b;l>=x[i];l--)
			f[i][j][l]=f[i-1][j-1][l-x[i]] || f[i-1][j][l];
<span style="white-space:pre">			</span>//第i个数到底选不选,选:如果前i-1选j-1,可以组成l-x【i】,那么f【i】【j】【l】=1
<span style="white-space:pre">			</span>//不选x【i】:如果前i-1个数选j个,可以组成l,那就不用选x【i】<span style="white-space:pre">	</span>
			for (int l=0;l<x[i];l++) f[i][j][l]=f[i-1][j][l];//前l<x【i】不能选x[i],那么就只有,以上的不选x【i】的那种情况了
		}
		f[i][0][0]=1;//被这里坑了一段时间(没有考虑到,前i个数选0个,组成0是可以的)
	}
	/*for (int i=1;i<=5;i++)
	for (int j=1;j<=k;j++)
	for (int l=0;l<=18;l++) printf("%d %d %d %d\n",i,j,l,f[i][j][l]);
		*/
	int ans=0,last=0,now;
	for (int i=1;i<a;i++) if (f[n][k][i]) last=i;
	
	for (int i=a;i<=b;i++)
	if (f[n][k][i]) 
	{
		if (last<a)//t在最小的时候,t==a时
		{
			ans=min(a-last,i-a);
			last=i;
		}
		ans=max(ans,(i+last)/2-last),last=i;//t在两个数中间时,肯定方案最好
	}	//在这里想了半天
			
	ans=max(ans,b-last);//t在最大的时候t==b时
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:([置顶] 勿忘把春夏之交vijos的比赛再看看)