2024牛客暑期多校训练营1D「树状数组」2A「dp」

D-XOR of Suffix Sums_2024牛客暑期多校训练营1 (nowcoder.com)

思路:

突破点:值域范围不大,可用树状数组维护

如果直接维护后缀,那么每一次增加一个数的进位会使之前维护的二进制串失效,注意到 s u f [ i ] = p r e [ n ] − p r e [ i − 1 ] suf[ i ] = pre[ n ] - pre[i - 1] suf[i]=pre[n]pre[i1],每次新增一个数的 p r e [ n ] pre[ n ] pre[n]是易得的, p r e [ i − 1 ] pre[ i - 1 ] pre[i1]是已知的,所以我们可以维护前缀,答案为 ( p r e [ n ] − p r e [ 1 ] ) ⊕   ( p r e [ n ] − p r e [ 2 ] ) … … (pre[ n ] - pre[ 1 ]) \oplus \ (pre[ n ] - pre[ 2 ]) …… (pre[n]pre[1]) (pre[n]pre[2])……

考虑二进制枚举,模数是 2 21 2^{21} 221,只有低21位有效,对于每一个位只要知道上述表达式出现1的奇偶性即可,问题抽象为在 a + b a + b a+b中,固定a, a + b a+b a+b有没有第i位对b来说是一个连续的区间,忽略大于i的位,可以用树状数组维护b,也可以理解为权值线段树,即维护 − p r e [ i ] -pre[i] pre[i]

考虑复杂度,每次最多加一个点,即最多有5e5个点,删除也不会超过这个数,所以可以暴力遍历要删的东西从树状数组从删除。

AC码:
#include <bits/stdc++.h>
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second

using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 3e6 + 50;
const ll mod = 2097152;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
ll a[N], tr[25][N];
ll two[30];
ll lowbit(int x) { return x & -x; }

void add(ll x, int op) {
    for(int i = 0; i <= 20; i++){
        int xx = (two[i + 1] - x % two[i + 1]) % two[i + 1];
        if (xx == 0) {
            tr[i + 1][0] += op;
            continue;
        } while (xx <= two[i + 1]) {
            tr[i + 1][xx] += op;
            xx += lowbit(xx);
        }
    }
}
ll quer(ll x, ll i) {
    int ans = 0;
    while (x > 0) {
        ans += tr[i + 1][x];
        x -= lowbit(x);
    }
    return ans;
}

ll query(ll x) {
    ll res = 0, ans = 0;
    for (int i = 0; i <= 20; ++i) {
        int xx = x % two[i];
        if (x >> i & 1) {
            res = quer(two[i] - 1 - xx, i) + quer(two[i + 1], i) - quer(two[i + 1] - 1 - xx, i) + tr[i + 1][0];
        } else {
            res = quer(two[i + 1] - 1 - xx, i) - quer(two[i] - 1 - xx, i);
        }
        if (res & 1) ans |= 1ll << i;
    }
    return ans;
}

void work() {
    int q; cin >> q;
    int n = 0;
    add(0, 1);
    for (int i = 1; i <= q; ++i) {
        ll t, v; cin >> t >> v;
        for (int j = 1; j <= t; ++j) {
            add(a[n], -1);
            a[n] = 0;
            n--;
        }
        n++;
        a[n] = (a[n - 1] + v) % mod;
        cout << query(a[n]) << '\n';
        add(a[n], 1);
    }
}

signed main() {
    io;
    two[0] = 1;
    for (int i = 1; i <= 21; ++i) {
        two[i] = two[i - 1] * 2;
    }
    int t = 1;
//     cin >> t;
    while (t--) {
        work();
    }
    return 0;
}
/*
4
0 15
1 9
0 5
0 14

15
9
11
1
*/

G-The Set of Squares_2024牛客暑期多校训练营2 (nowcoder.com)

思路:

突破点:注意到1000以内的数包含的质数可以分为大质数(最多只能出现一次)和小质数(可以重复出现),按照大质数分组遍历,对小质数的状态进行状压背包。

定义 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示在第i个位置,当前大质数的奇偶性(j为1是奇),状态为k时的答案。

每次加入一个数t,令t的质数状态为st,转移方程为:

d p [ i ] [ j ] [ k ⊕ s t ] + = d p [ i − 1 ] [ j ] [ k ⊕ t ] dp[i][j][k\oplus st] += dp[i - 1][j][k\oplus t] dp[i][j][kst]+=dp[i1][j][kt](不选第i个数,直接由上一个数转移来) + d p [ i − 1 ] [ j ⊕ 1 ] [ k ] × t +dp[i - 1][j \oplus 1][k] \times t +dp[i1][j1][k]×t中有的平方质数的贡献(作为t的单独贡献) [ 1 ] ×   k & s t ^{[1]}\times \ k \& st [1]× k&st 状态下包含的质数积(作为tk的共同贡献) [ 2 ] × ^{[2]}\times [2]× 大质数(当j从1变为0时)。

上述 [ 1 ] [ 2 ] [1][2] [1][2]式都可以预处理出来,转移时直接用。

每一个dp状态下都包含了全部(选或不选任何一个位置)情况的答案,队友说我讲不清楚,还是自己悟一下吧。

最后的 d p [ n ] [ 0 ] [ 0 ] − 1 dp[n][0][0] - 1 dp[n][0][0]1(减去什么都不选的空集)就是最终答案。

写法注意:

  • 第一维可以滚动
  • 处理时只加入小质数贡献,大质数我们在dp中单独算,而且需要处理的下标范围不一样需要注意
  • 在每一组dp结束后要清空没用的东西
  • 如果当前所在的组是无大质数组,那么所有的 d p [ i ] [ 1 ] [ k ] dp[i][1][k] dp[i][1][k]都要计入 d p [ i ] [ 0 ] [ k ] dp[i][0][k] dp[i][0][k]
AC码:
#include <bits/stdc++.h>
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define pb push_back
#define pii pair<int,int>
#define fi first
#define se second

using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2060;
const int M = 1000;
const ll mod = 1e9+7;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
bool is[N];
int tot, prime[N], a[N];
ll tval[N], state[N], cal[N];//i单独的贡献,i的状压状态,状态i表示的数
ll dp[2][2][N];

void euler_sieve() {
    tot = 0;
    prime[tot++] = 1;
    for (int i = 2; i < N; ++i) {
        if (!is[i]) prime[tot++] = i;//0为素数
        for (int j = 1; j < tot && prime[j] * i < N; ++j) {
            is[prime[j]*i] = 1;
            if (i % prime[j] == 0)
                break;
        }
    }
}

void init() {
    for (int i = 0; i < N; ++i) tval[i] = cal[i]= 1;

    for (int i = 1; i <= M; ++i) {
        int x = i;
        for (int j = 1; j <= 11; ++j) {
            int p = prime[j];
            while (x % p == 0) {
                x /= p;
                if ((state[i] >> (j - 1)) & 1) tval[i] = (tval[i] * p) % mod;
                state[i] ^= (1 << (j - 1));
            }
        }
    }
    for (int i = 0; i < (1 << 11); ++i) {
        for (int j = 11; j >= 1; --j) {
            if (i >> (j - 1) & 1) cal[i] = (cal[i] * prime[j]) % mod;
        }
    }
}

void work() {
    int n;
    cin >> n;
    vector<int> have[N];
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        int x = a[i];
        for (int j = 1; j <= 11; ++j) {
            int p = prime[j];
            while ((x % p) == 0) {
                x /= p;
            }
        }
        if (x == 1) have[0].pb(a[i]);
        else have[x].pb(a[i]);
    }
    
    int now = 1;
    dp[now][0][0] = 1;
    for (int i = 0; i <= 1000; ++i) {
        if (have[i].size() == 0) continue;
        for (auto t: have[i]) {
            now ^= 1;
            int x = state[t];
            memset(dp[now], 0, sizeof(dp[now]));
            for (int k = 0; k < (1 << 11); ++k) {
                dp[now][0][k ^ x] = (dp[now][0][k ^ x] + dp[now ^ 1][0][k ^ x] + dp[now ^ 1][1][k] * tval[t] % mod * cal[k & x] % mod * (i == 0 ? 1 : i) % mod) % mod;
                dp[now][1][k ^ x] = (dp[now][1][k ^ x] + dp[now ^ 1][1][k ^ x] + dp[now ^ 1][0][k] * tval[t] % mod * cal[k & x] % mod) % mod;
            }
        }
        if (i == 0) {
            for (int k = 0; k < (1 << 11); ++k) dp[now][0][k] = (dp[now][0][k] + dp[now][1][k]) % mod;
        }
        memset(dp[now][1], 0, sizeof(dp[now][1]));
    }
    cout << (dp[now][0][0] - 1 + mod) % mod << '\n';
}

signed main() {
    io;
    int t = 1;
//  cin >> t;
    euler_sieve();
    init();
    while (t--) {
        work();
    }
    return 0;
}
/*
598
41 13 14 43 1 9 16 17 56 46 54 14 75 5 86 58 33 23 39 55 27 17 34 83 41 56 18 65 95 37 56 48 1 54 63 88 56 55 13 55 95 82 56 74 15 7 74 45 67 89 2 62 45 23 64 86 3 96 77 7 19 63 44 57 99 64 71 81 37 69 28 9 48 74 72 38 6 74 7 72 60 19 42 17 29 49 28 83 83 28 50 37 61 78 59 46 11 34 36 74 17 72 97 7 24 90 14 90 27 96 67 75 19 1 69 69 96 24 27 15 62 76 57 28 25 69 88 36 69 8 40 4 30 91 25 58 78 35 63 10 78 15 50 59 53 20 6 48 49 3 44 32 91 97 27 82 71 24 84 49 46 59 50 74 74 54 3 89 34 35 21 63 80 86 22 35 93 22 69 68 100 2 99 57 71 14 87 41 20 63 65 55 9 14 59 96 13 25 23 60 97 20 34 92 31 10 29 55 71 25 15 68 73 22 28 18 32 87 81 92 94 47 65 45 46 25 89 58 97 5 53 11 33 49 33 42 33 65 78 91 15 57 22 89 44 62 75 3 12 16 93 24 64 6 75 88 93 20 90 84 12 33 62 70 96 31 15 51 1 57 81 92 73 24 64 45 34 10 53 85 90 49 60 43 63 10 79 67 83 80 57 67 84 77 97 94 49 88 11 21 36 6 50 16 50 52 53 14 30 78 69 39 90 47 52 31 40 99 42 85 39 100 11 82 56 29 26 82 1 2 77 83 5 94 50 6 73 52 55 47 32 34 23 77 77 37 27 4 56 15 27 57 69 4 26 29 93 70 69 54 62 96 29 5 100 31 76 83 82 100 64 20 89 31 92 43 34 68 89 44 18 20 6 76 89 36 24 69 17 19 99 83 70 3 83 32 57 80 16 76 78 68 15 6 20 60 15 14 61 10 68 29 27 33 51 32 4 41 100 60 82 64 15 56 49 5 40 99 40 41 63 54 20 97 48 76 17 13 64 89 22 57 38 42 81 10 75 40 89 86 51 52 71 41 17 41 1 4 71 46 22 10 68 16 32 30 36 2 96 35 3 96 18 68 58 35 78 60 1 84 40 31 23 5 49 63 74 10 68 43 32 77 30 68 7 79 78 47 53 8 42 93 49 11 51 91 4 48 5 34 13 64 89 64 30 69 70 61 96 51 97 55 47 44 64 26 32 5 78 81 57 39 21 87 16 95 55 15 1 82 31 28 95 25 9 30 71 34 1 83 77 25 44 78 41 32 13 92 15 68 81 62 28 62 75 73 62 11 38 38 88 64 70 78 32 34 87 19 3 19 10 9 28 43 13 54 73 92 91 22 85 59 78 88 76 77 80 47

552436798
*/
  • 26
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值