给定1e5个数,要求是否能选出一个子序列,使得子序列的和为n的倍数。
由鸽巢原理和前缀和,如果子序列的长度大于等于n,那么答案一定是YES,小于n就需要借助dp来判断了。
这里又是bitset的奇淫技巧。思想是,普通的状态可达dp式子是f[ i ][ j ] = f[ i - 1][ j ] | f[ i - 1][ j - a[ i ] ].也就是继承上一层同一位置的j或上一层不同位置的j-a[i]。因此可以用移位代替减法。
普通dp求解1953ms,比赛时评测机稍微卡一下可能就TLE了,比赛时可能要加几个特判才能过。bitset优化后83ms。
题目链接:求3600的倍数
优化解法:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int a[N];
bitset<3605> f;
signed main()
{
int tt;
cin >> tt;
while(tt--){
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] %= 3600;
}
if (n >= 3600) {
puts("YES");
// continue;
}
else {
f[0] = 1;
for (int i = 1; i <= n; i++){
f |= ((f << a[i]) | (f >> (3600 - a[i])));
}
if (f[3600]) puts("YES");
else puts("NO");
f.reset();
}
}
return 0;
}
普通做法:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5, mod = 998244353;
int a[N];
int f[2][N];
signed main()
{
int tt;
cin >> tt;
while(tt--){
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] %= 3600;
}
if (n >= 3600) {
puts("YES");
// continue;
}
else {
int INF = 1e18;
fill(f[0], f[0] + 3600, -INF);
fill(f[1], f[1] + 3600, -INF);
f[0][0] = 0;
for (int i = 1; i <= n; i++){
for (int j = 0; j < 3600; j++){
f[1][j] = max(f[0][j], f[0][((j - a[i]) % 3600 + 3600) % 3600] + 1);
// cout << f[1][j] << " ";
}
// cout <
for (int j = 0; j < 3600; j++) f[0][j] = f[1][j];
}
if (f[0][0]) puts("YES");
else puts("NO");
}
}
return 0;
}