洛谷 P9814 [CCC 2015 S5] Greedy For Pies

传送门

题意

给你两个序列,你可以从序列二中取出一些数插在序列二的任何位置。

此后,你可以从新的序列中取出一些不相邻的数,问你取出的数的和的最大值是多少。

思路

一眼动态规划。

假设第一个序列长为 n n n,第二个序列长为 m m m

定义: d p i , j , k , x , y dp_{i,j,k,x,y} dpi,j,k,x,y 表示目前枚举到第 i i i 个数,有 j j j 个相邻的数要选,有 k k k 个相邻的数两个都不选时的和的最大值。其中 x x x 表示目前这个数选不选( 0 0 0 表示不选, 1 1 1 表示选), y y y 表示整个序列的第一个数选不选 ( 0 0 0 表示不选, 1 1 1 表示选)。

为了保证有解,我们要使 j j j 的值必须小于等于 m m m,而 k k k 的值可以大于 m m m,因为相邻两个数不选的情况也是不相邻。

一旦 k k k 的值等于 m + 1 m+1 m+1 后,我们就不用管 k k k m + 1 m+1 m+1 更大的情况了,因为此时 k k k 的值对最终答案没有影响。

我们可以先取出序列一中的数,找出每一个情况的和的最大值,并且要拿出 j j j 个序列二中的数来当中间的数,因为对答案没有影响,又要使情况最优,所以要优先拿小的数。

接下来,我们可以把序列二剩下的数优先填入两个都不选的中间位置,也就是 k k k 个位置,因为对答案有影响,又要使情况最优,所以要优先拿大的数。

设剩下的序列二中的数有 s s s 个,这 s s s 个数可以插到开头或结尾的位置。

如果开头的数选,结尾的数不选,则应该把剩下的数插到结尾位置,对答案有贡献的数有 s 2 \frac {s}{2} 2s(向上取整)个数。

如果开头的数不选,结尾的数选,则应该把剩下的数插到开头位置,对答案有贡献的数有 s 2 \frac {s}{2} 2s(向上取整)个数。

如果开头的数选,结尾的数也选,则应该把剩下的数插到开头或结尾的任意位置,对答案有贡献的数有 s 2 \frac {s}{2} 2s(向下取整)个数。

如果开头的数不选,结尾的数也不选,则应该把剩下的数平均分配到开头和结尾的位置,对答案有贡献的数有 s 2 + 1 \frac {s}{2}+1 2s+1(向下取整)个数。

为了使答案最优,我们要在剩下的序列二的数中取尽量大的数,最后再加上满足条件的 dp 数组中的情况就是答案了。

AC 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,a[3005],b[105],zans=0,dp[2][105][105][2][2];//全部数量,00个数,11个数,目前的01,第一个数的01 
bool cmp(int x,int y)
{
	return x>y;
}
int qh(int i,int j,int x,int y)
{
	int o=m-j,sum=0;
	for(int s=1;s<=min(o,i);s++)
	{
		sum+=b[s];
	}
	if(o<=i)
	{
		return sum;
	}
	int p=m-min(o,i)-j;
	int f=p+(!x)+(!y);
	for(int s=min(o,i)+1;s<=min(o,i)+f/2;s++)
	{
		sum+=b[s];
	}
	return sum;
}
signed main()
{
	scanf("%lld",&n); 
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
	}
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld",&b[i]);
	}
	sort(b+1,b+m+1,cmp);
	for(int i=1;i<=n;i++)
	{
		memset(dp[i%2],128,sizeof(dp[i%2]));
		if(i==1)
		{
			dp[1][0][0][0][0]=0;
			dp[1][0][0][1][1]=a[1];
			continue;
		}
		for(int j=0;j<=m+1;j++)
		{
			for(int k=0;k<=m;k++)
			{
				if(j==0)
				{
					dp[i%2][j][k][0][0]=dp[(i-1)%2][j][k][1][0];
					dp[i%2][j][k][0][1]=dp[(i-1)%2][j][k][1][1];
				}
				else
				{
					dp[i%2][j][k][0][0]=max(dp[(i-1)%2][j-1][k][0][0],dp[(i-1)%2][j][k][1][0]);
					dp[i%2][j][k][0][1]=max(dp[(i-1)%2][j-1][k][0][1],dp[(i-1)%2][j][k][1][1]);
				}
				if(j==m+1)
				{
					dp[i%2][j][k][0][0]=max(dp[(i-1)%2][j][k][0][0],dp[i%2][j][k][0][0]);
					dp[i%2][j][k][0][1]=max(dp[(i-1)%2][j][k][0][1],dp[i%2][j][k][0][1]);
				}
				if(k==0)
				{
					dp[i%2][j][k][1][0]=dp[(i-1)%2][j][k][0][0]+a[i];
					dp[i%2][j][k][1][1]=dp[(i-1)%2][j][k][0][1]+a[i];
				}
				else
				{
					dp[i%2][j][k][1][0]=max(dp[(i-1)%2][j][k][0][0],dp[(i-1)%2][j][k-1][1][0])+a[i];
					dp[i%2][j][k][1][1]=max(dp[(i-1)%2][j][k][0][1],dp[(i-1)%2][j][k-1][1][1])+a[i];
				}
			}
		}
	}
	for(int i=0;i<=m+1;i++)
	{
		for(int j=0;j<=m;j++)
		{
			for(int x=0;x<=1;x++)
			{
				for(int y=0;y<=1;y++)
				{
					if(dp[n%2][i][j][x][y]>0)
					{
						int ans=dp[n%2][i][j][x][y];
						zans=max(zans,ans+qh(i,j,x,y));
					}
				}
			}
		}
	}
	cout<<zans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值