P7535 [COCI2016-2017#4] Kas 题解
校内模拟赛的题
思路
看到这道题可以很快想出一个 \(O(n (\sum c_i)^2)\) 的方法:用 \(dp_{i,j,k}\) 表示表示第 \(1 \sim i\) 张钞票里,能否使得 Kile 分到 \(j\) 块钱,Pogi 分到 \(k\) 块钱,可以得到状态转移方程:
\[ dp_{i,j,k}|= \begin{cases}
dp_{i-1,j,k}\\
dp_{i-1,j-c_i,k}\\
dp_{i-1,j,k-c_i}
\end{cases}\]
但是这样空间要开到 \(500\times 500\times 10^5\) ,会爆空间。所以我们再观察一下,可以用一个显然的结论来优化空间:知道两个数的和、差就可以求出这两个数的值。我们重新定义一下 \(dp\) 数组,令 \(dp_{i,j}\) 表示第 \(1 \sim i\) 张钞票里, Kile 和 Pogi 分到钱的差值为 \(j\) 时,两人钱数之和的最大值, 可以得到柿子:
\[ dp_{i,j}= \max\begin{cases}
dp_{i-1,|j-c_i|}+c_i\\
dp_{i-1,j}\\
dp_{i-1,j+c_i}+c_i
\end{cases}\]
注意到每次转移第一维只从 \(i-1\) 转移过来,可以用滚动数组优化,不过不加也可以过。
代码
#include<bits/stdc++.h>
using namespace std;
int n,a[600],dp[505][100005],tot,now;
int main(){
memset(dp,-0x7f,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
tot+=a[i];
}
dp[0][0]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=tot;j++){
dp[i][j]=max(dp[i-1][j],max(dp[i-1][j+a[i]]+a[i],dp[i-1][abs(j-a[i])]+a[i]));
}
}
cout<<tot-dp[n][0]/2;
return 0;
}