牛客刷题记录

1.失衡天平

 思路:DP

一开始什么办法都想不到,就开始考虑DP,然后可以去定义一个两维的状态转移方程

f [i] [j] 表示在前 i 个物品里面选,天平两边相差为 j 的 的所有方案 在这个状态定义下求最大值

那么答案肯定就是 在前n个物品里面选 天平两边相差为 0 到 m 的所有方案的最大值 状态表示为

f [n] [j]  0<=j<=m

那么状态转移方程就是

f [i] [j]= max( f[i-1] [j],  f[i-1][ j-a[i] ] + a[i] );

第一项为从前 i 个选,但是不选第 i 项 第二项为选上第 i 项 并且把这个放在天平比较轻的一边

由于 j-a[i] 有可能是负数,就得取绝对值

第二个转移为

f [i] [j]= max( f[i-1] [j],  f[i-1][ j+a[i] ] + a[i] );

第一项为从前 i 个选,但是不选第 i 项 第二项为选上第 i 项 并且把这个放在天平比较重的一边

这样就可以枚举所有情况了

分析一下时间复杂度

第一维状态是 n 的,第二维是相差 j 而 j 的最大取值是 n*m 

那么时间复杂度就是 O( n*m*m ) 

#include <bits/stdc++.h>
using namespace std;
const int N=110;
int a[N];
int f[N][N*100+10];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    memset(f,-0x3f,sizeof f);
    ///这里把f数组弄成-0x3f3f3f3f是因为怕有些状态从不合法状态转移
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=N*100+5;j++)
        {
            f[i][j]=max(f[i-1][abs(j-a[i])]+a[i],f[i-1][j]);
            f[i][j]=max(f[i][j],f[i-1][j+a[i]]+a[i]);
        }
    }
    int res=-1;
    for(int i=0;i<=m;i++)
        res=max(res,f[n][i]);
    cout<<res<<endl;
    return 0;
}

2.Steadily Growing Steam

本题为2021 ICPC上海站的原题

题意:有n张牌,每张牌都有一个点数和价值,首先你可以选m张牌(可以小于m张)使得这些牌的点数翻倍,然后把这n张牌分入两个集合,使得两个集合的点数之和相同,求价值之和最大

思路

有上一题为基础 在看这题就会变得有点简单了,也是和失衡天平一样

定义一个三维的状态 f [i] [j] [k] 为在前 i 个物品里面选,并且使用 j 次技能,两组相差为 k 的所有方案

求的是价值之和最大值

首先为什么本题比失衡天平多了一个状态就是因为是本题多了一个使用技能的操作

状态转移

1、不选第i张牌:f[i][j][k]=f[i−1][j][k]

2 、 选 择 第 i 张 牌 放 入 a 组 , 且 不 使 用 技 能 : f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i ] [ j ] [ k − t [ i ] ] + v [ i ] ) 

3 、 选 择 第 i 张 牌 放 入 b 组 , 且 不 使 用 技 能 : f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i ] [ j ] [ k + t [ i ] ] + v [ i ] ) 

4 、 选 择 第 i 张 牌 放 入 a 组 , 且 使 用 技 能 : f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i ] [ j − 1 ] [ k − 2 ∗ t [ i ] ] + v [ i ] ) 

5 、 选 择 第 i 张 牌 放 入 b 组 , 且 使 用 技 能 : f [ i ] [ j ] [ k ] = m a x ( f [ i ] [ j ] [ k ] , f [ i ] [ j − 1 ] [ k + 2 ∗ t [ i ] ] + v [ i ] ) 

一共五个状态

然后三维需要的空间太大了 就用滚动数组优化第一维

然后最后一维两组相差为 k 的想法会出现负数 

然后也分析一下 k 的取值大小的问题 每个牌最多13点翻倍以后就变成了26点一共有100张牌

那么k最大去到2600

最小呢可以去到-2600

并且数组下标不可以出现负数 那么就把k映射到0到5200就好了 把2600看成0

本题用上面处理得当方法应该也是没问题的,这里提供第二种方法

如果使用上面的方法 状态应该也会相应减少

时间复杂度为 O( n*5200 )

最后答案就是 f [n] [ j ] [ 0 ] ;   0<= j <= m 

#include <bits/stdc++.h>
using namespace std;
const int N=105,M=6000,INF=0x3f3f3f3f;
typedef long long ll;
ll f[2][N][M];
int v[N],t[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i]>>t[i];
    for(int i=0;i<=m;i++)
        for(int j=0;j<=5200;j++)
            f[0][i][j]=-1e18*(j!=2600);
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
        {
            for(int k=0;k<=5200;k++)
            {
                int x=i&1;
                f[x][j][k]=f[x^1][j][k];
                if(k-t[i]>=0)
                    f[x][j][k]=max(f[x][j][k],f[x^1][j][abs(k-t[i])]+v[i]);
                if(k+t[i]<=5200)
                    f[x][j][k]=max(f[x][j][k],f[x^1][j][k+t[i]]+v[i]);
                if(j)
				{
				    if(k-2*t[i]>=0)
                        f[x][j][k]=max(f[x][j][k],f[x^1][j-1][abs(k-2*t[i])]+v[i]);
                    if(k+2*t[i]<=5200)
                        f[x][j][k]=max(f[x][j][k],f[x^1][j-1][k+2*t[i]]+v[i]);
				}
            }
        }
    }
    ll res=-1;
    for(int i=0;i<=m;i++)
        res=max(res,f[n&1][i][2600]);
    cout<<res<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值