[题解]GYM 100506C Cutting Banknotes
题意
给定一个数 \(x\),以及 \(n\) 种不同面值的钞票 \(a_i\)。(其中 \(x\) 可能是一个小数,但是 \(a_i\) 一定是整数)
你可以将 \(a_i\) 除以 \(2\),得到新的面值。
如果你能通过这种方式,能够凑出 \(x\),输出 yes,否则输出 no。
思路
首先,有一个显然的结论:如果 \(x\) 不是形如 \(a.00,a.25,a.50,a.75\) 的数,那么一定无解。
剩下的我的第一反应是一个完全背包。
首先将 \(a_i\) 一直除以 \(2\),直到不能被 \(2\) 整除,再除以 \(8\)。
这样一定能保证新的 \(a_i\) 能够取若干个得到原来的 \(a_i\),使得结果不变。
但是,这样的处理会产生小数,所以用了一个 unordered_map 来搞。
显然,这样时间复杂度为错。
那么考虑将原本的 \(x\) 乘上 \(8\),这样能保证结果不变,且时间复杂度少 \(\Theta(\log n)\)。
但是,有一组 hack。
这组数据明显是输出 yes,因为 \(5 + 11 = 16\),\(16\) 能够通过一直除以 \(2\) 得到 \(1\)。但是,按照这种思路会输出 no。
那么,我们将值域开大一点,改为 \(x \times 64\) 就行。
Code
#include <bits/stdc++.h>
#define re register
using namespace std;
const int N = 1010,M = 1e6 + 10;
const double eps = 1e-6;
int T,n,m;
int arr[N];
bool dp[M];
double x;
inline int read(){
int r = 0,w = 1;
char c = getchar();
while (c < '0' || c > '9'){
if (c == '-') w = -1;
c = getchar();
}
while (c >= '0' && c <= '9'){
r = (r << 3) + (r << 1) + (c ^ 48);
c = getchar();
}
return r * w;
}
inline bool cmp(double a,double b){
return (fabs(a - b) <= eps);
}
inline bool check(double x){
int A = x;
double B = x - A;
return (cmp(B,0.0) || cmp(B,0.25) || cmp(B,0.5) || cmp(B,0.75));
}
int main(){
T = read();
while (T--){
memset(dp,false,sizeof(dp));
dp[0] = true;
scanf("%lf",&x);
n = read();
m = x * 64;
if (!check(x)){//特判无解
puts("no");
for (re int i = 1;i <= n;i++) arr[i] = read();
continue;
}
for (re int i = 1;i <= n;i++){
arr[i] = read();
while (!(arr[i] & 1)) arr[i] >>= 1;//处理新的值
}
sort(arr + 1,arr + n + 1);
int nn = unique(arr + 1,arr + n + 1) - arr - 1;//离散化,经试验在一般情况下能将 n 个元素降低为 n / 2 个元素
for (re int i = 1;i <= nn;i++){//完全背包
for (re int j = arr[i];j <= m;j++) dp[j] |= dp[j - arr[i]];
if (dp[m]) break;
}
if (dp[m]) puts("yes");
else puts("no");
}
return 0;
}

浙公网安备 33010602011771号