bitset优化dp

给定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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值