题解:P10527 [XJTUPC2024] 最后一块石头的重量
hgckythgcfhk · · 题解
鉴于大家对题解区现有的唯一一篇题解评价并不好,我决定写一篇详细的题解。
注意到本题是一个背包。
这句话省略了很多东西,感觉和没说一样。
首先先说为什么是个背包,手搓样例
具体的,我给一下样例
第一次操作:
40 21 ->19
第二次操作:
33 31 ->2
此时,我们剩下的石子的状态是:
2 19 26
然后我们就可以简简单单的用小的消耗大的。
26 19 ->7
7 2 ->5
于是我们发现,我们有时候需要利用一个大的和一个小的制造一个相对中等的,然后用这个中等的去继续操作。
于是,我们不难想到用字母一般化这个过程,推一推式子,主要是实在想不出来该干点什么。
以
样例
到这里也许我说的是废话,但是下面就有用了。
拆开括号:
这个不好看,换个顺序:
有没有发现,这些操作下来相当于把所有数分成两组,求两组的差的最小值。
当然这个东西可以用更复杂的例子去验证,自己动手并不麻烦,但会大幅度增加题解的篇幅,所以我不作展示,总是,这个结论的正确性是很好理解的。
于是我就想起来一个题,做法是随机打乱数组,然后用一种正确率还行的贪心,这样进行很多次取最小值,具体这个贪心怎么写不做赘述,感兴趣的看这题。
但是,这个正确率放在本题的数据范围下并不怎么样。
于是想到背包,具体的,状态转移方程如下:
时间复杂度
于是考虑 bitset 优化,优化需要我们写成一个方便位运算的形式,首先得写成
dp[i]=(dp[i-1]<<a[i])|(dp[i-1]>>a[i])
但是,我们还漏了一种情况,我们可以用新加进来的石子减前面的石子,于是有:
for(int k=0;a[i];--a[i],++k)dp[f][k]=dp[f][k]|dp[f^1][a[i]];
这里加了滚动数组优化,否则会 MLE。
于是可以写出程序。
signed main(){open;int n,a;cin>>n;dp[0][0]=1;
for(int i=1;i<=n;++i){rg const bool f=i&1;cin>>a;
dp[f]=(dp[f^1]<<a)|(dp[f^1]>>a);
for(int k=0;a;--a,++k)dp[f][k]=dp[f][k]|dp[f^1][a];
}for(int i=0;i<5001;++i)if(dp[n&1][i])return cout<<i,0;
}
这个程序会在 #4 出错,经过我大量实验发现,是因为 bitset 需要开的很大,但是我们开的不够大,正确的做法是随机打乱一下