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;
}