挑选pick

挑选pick
题目在此继续省略(条件原因,有乱码)

那就概括一下题目大意:
n个小朋友,1~n从左到右排成一行,每个小朋友都有一个气场值ci和一个实力值wi.
假如丁爷爷选了第i个小朋友,就会将这个小朋友及他右边的ci个小朋友踢出。
现在要求能选到的最大的实力值和选的方案总数,方案总数模998244353.
这道题第一感觉就是dp,可是却看错了题目,最后转成暴力时间就不够了,最大实力值对了可没时间求方案数,真的是...丧心病狂啊!...那就讲满分算法吧,好打易懂。


由这个题意似乎可以往一种常见的dp去想那就是---括号序列!
这里我们显然可以转化成若第i个填左括号,那么就要有ci个右括号来与之匹配。
那不就非常简单了吗?!
f[i][j]表示前i个小朋友中左括号与右括号的差为j的最大实力值。
则f[i][j]=max{f[i-1][j+1](取右括号情况),f[i-1][j-c[i]]+w[i](取左括号情况)}
if j==0 f[i][j]=max{f[i][j],f[i-1][j]}可以不取。
辣么最后的结果就是f[n][0]啦~
初值f[0][0]=0就ok啦。
g[i][j]表示前i个小朋友中左括号与右括号的差为j的方案总数。
也是一样的道理,将max改成累加即可。


这道题的变形有点考思路,但变形好之后就很基本了qwq下次要记住啊...




#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#define N 5050
#define ll long long
#define mod 998244353
using namespace std;
int n,now,w[N],c[N];
ll f[2][N],g[2][N];
int main()
{
	freopen("pick.in","r",stdin);
	freopen("pick.out","w",stdout); 
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=n;i++) scanf("%d",&c[i]);
	f[0][0]=0;now=0;
	for(int i=1;i<=n;i++){
		now=1-now;
		for(int j=0;j<=n;j++){
			if(!j){
				f[now][j]=max(f[1-now][j],f[1-now][j+1]);
				if(j>=c[i]) f[now][j]=max(f[now][j],f[1-now][j-c[i]]+w[i]);
			}else{
				f[now][j]=f[1-now][j+1];
				if(j>=c[i]) f[now][j]=max(f[now][j],f[1-now][j-c[i]]+w[i]);
			}
		}
	}
	printf("%lld ",f[now][0]);
	g[0][0]=1;now=0;
	for(int i=1;i<=n;i++){
		now=1-now;
		for(int j=0;j<=n;j++){
			if(!j){
				g[now][j]=(g[1-now][j]+g[1-now][j+1])%mod;
				if(j>=c[i]) g[now][j]=(g[now][j]+g[1-now][j-c[i]])%mod;
			}else{
				g[now][j]=g[1-now][j+1];
				if(j>=c[i]) g[now][j]=(g[now][j]+g[1-now][j-c[i]])%mod;
			}
		}
	}
	printf("%lld\n",g[now][0]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值