LG15208 [NWERC 2025] Bisecting Bargain 题解
LG15208 [NWERC 2025] Bisecting Bargain 题解
取款机可支付任意由面值 \(1,2,5,10,20,50,100,200,500\) 组成的金额,总和为 \(n\)。给定 \(n\)(\(1\le n\le10000\)),判断是否无论取款机如何支付,金额总能均分为两堆各 \(n/2\)。若是,输出
splittable;否则输出一组总和为 \(n\) 且无法均分的硬币/纸币:先输出数量,再输出各面值(任意一组即可)。
给一种最暴力也是最短的做法
首先可以得到观察:
- \(1,5,10,50,100\) 的钞票在最终的分配方案中至多出现 \(1\) 次,否则每对于两张面值为 \(1,5,10,50,100\) 的钞票,不如改为选取一张 \(2,10,20,100,200\) 的钞票。
- \(2,20\) 元的钞票在最终的分配方案中至多出现 \(4\) 次,否则每对于五张面值为 \(2,20\) 的钞票,不如改为选取一张 \(10,100\) 的钞票。
我们不管 \(200,500\) 的钞票有没有更优秀的性质,由于 \(n\leq 10000\),\(200\) 的最多选取 \(50\) 张,\(500\) 的最多选取 \(20\) 张。
综上,我们至多只用考虑 \(2^5\times 5^2\times 51\times 21\approx 8.6\times 10^5\) 种情况。实际上完全达不到这个上界。
然后我们在枚举的过程中用背包统计当前的方案能否取到某个值,即定义 \(f_i=1/0\) 表示当前的选取方案能或否组成 \(i\) 的面额,最后如果 \(f_{n/2}=0\) 则表明无法平均分配。
假设当前考虑一张面值为 \(v\) 的钞票加入到方案中,设新方案的背包状态为 \(g\),有转移
相当于 \(f_i\) 中 \(f_{i+v}\) 有一个为真,则 \(g_{i+v}\) 为真,这是或的关系,且对于每个 \(i\) 都有这样的关系,考虑使用 std::bitset 优化这个背包转移。可写作
g=f|(f<<v);
于是单次转移的复杂度为 \(O(\frac{n}{w})\)。计算得总的运行上界约为 \(1.3\times 10^8\) 次计算。实际加上剪枝后跑得飞快,单点最慢 \(6\) 毫秒;而且不加剪枝这个实际也无法取到这个上界,每个点也不超过 \(200\) 毫秒。
#include <bits/stdc++.h>
using namespace std;
const int N=10004;
const int v[10]={0,1,2,5,10,20,50,100,200,500};
const int r[10]={0,1,4,1, 1, 4, 1, 1, 50, 20};
int n;
int stk[N],top;
void dfs(int i,int s,bitset<N>&f){
bitset<N>g=f;
if(s==n){
if(!f[n/2]||n%2==1){
printf("%d\n",top);
for(int i=1;i<=top;++i){
printf("%d ",stk[i]);
}puts("");
exit(0);
}
}
if(s>n||i>9)return;
if(i>=7&&(s%100)!=(n%100))return;//考虑最后枚举200和500时若个十位不同则无法满足,剪枝
for(int j=0;j<=r[i];++j){
for(int k=1;k<=j;++k)stk[++top]=v[i];
dfs(i+1,s+j*v[i],f);
top-=j;
f=f|(f<<v[i]);
}
f=g;
}
int main(){
scanf("%d",&n);
bitset<N> tmp;tmp[0]=1;
dfs(1,0,tmp);
puts("splittable");
return 0;
}
最后正如官方题解所言,此题考虑暴力优化比贪心会更加容易一些。

浙公网安备 33010602011771号