2020-ccpc-weihai

 ACM  线段树  分组背包  数论 󰈭 1555字

G 线段树 区间修改、取模,区间哈希和查询

需要维护一个支持区间加1并对固定模数取模、区间哈希查询的线段树

开局就想写的一道题,写到最后艰难调出来后wa7,最终放弃上机辅助队友看别的题

晚上回去又调了很久,发现了几个错误

  • 线段树哈希写错了,在求哈希和的时候不像普通的线段树区间和一样,查询哈希和时查询的区间[L, R]是需要动态变化的,必须保证L被不断地截断,才能保证算法的正确性
  • 眼睛比较瞎,前缀和pre[]对M(65536)取模了
  • 最开始的purify(将那些超过65536的位置取模)写挫了,在终止条件(l == r)之前就调用了down函数。调了一年才发现原来是这里有问题:如果在终止条件之前就调用down函数,这会导致地址的越界访问。因为st只开了4倍空间,所以如果在那种边角叶子结点(在满二叉树上多一个节外生枝的结构)仍然尝试down下去,就会访问到8倍的位置!在CF居然没有发生RE报警,以至于我一度怀疑是自己算法写错了,其实是空间的计算错误导致了UKE。
cpp
  1#define ls (p << 1)
  2#define rs ((p << 1) | 1)
  3#define mid ((l + r) >> 1)
  4
  5using namespace std;
  6const int maxn = 5e5 + 10;
  7const int M = 65536;
  8
  9const int pp = 999983;
 10const int mod = 1000237529;
 11
 12int a[maxn];
 13ll tag1[maxn << 2], st2[maxn << 2], st[maxn << 2];
 14ll po[maxn], pre[maxn];
 15
 16void up(int p, int l, int r){
 17    st[p] = st[ls] + st[rs] * po[mid - l + 1] % mod; st[p] %= mod;
 18    st2[p] = max(st2[ls], st2[rs]);
 19}
 20
 21void build(int p, int l, int r, int k, int x){
 22    if(l == r && l == k){
 23        st[p] = st2[p] = x;
 24        return ;
 25    }
 26    if(k <= mid) build(ls, l, mid, k, x);
 27    else build(rs, mid + 1, r, k, x);
 28    up(p, l, r);
 29}
 30
 31void down(int p, int l, int r){
 32    if(tag1[p]){
 33        tag1[ls] += tag1[p];
 34        tag1[rs] += tag1[p];
 35        
 36        st[ls] += pre[mid - l] * tag1[p] % mod; st[ls] %= mod;
 37        st[rs] += pre[r - mid - 1] * tag1[p] % mod; st[rs] %= mod;
 38        
 39        st2[ls] += tag1[p];
 40        st2[rs] += tag1[p];
 41        
 42        tag1[p] = 0;
 43    }
 44}
 45
 46void update(int p, int l, int r, int L, int R){
 47    if(L <= l && r <= R){
 48        st[p] += pre[r - l]; st[p] %= mod;
 49        st2[p]++; tag1[p]++;
 50        return ;
 51    }
 52    down(p, l, r);
 53    if(L <= mid) udpate(ls, l, mid, L, R);
 54    if(R > mid) update(rs, mid + 1, r, L ,R);
 55    up(p, l, r);
 56}
 57
 58void purify(int p, int l, int r){
 59    if(st2[p] < M) return;
 60    down(p, l, r);
 61    if(l == r){
 62        st[p] %= M; st2[p] %= M;
 63        return ;
 64    }
 65    
 66    if(st2[ls] >= M) purify(ls, l, mid);
 67    if(st2[rs] >= M) purify(rs, mid + 1, r);
 68    up(p, l, r);
 69}
 70
 71ll hsh(int p, int l, int r, int L, int R){
 72    if(L <= l && r <= R) return st[p];
 73    down(p, l, r);
 74    
 75    if(R <= mid) return hsh(ls, l, mid, L, R);
 76    else if(L > mid) return hsh(rs, mid + 1, r, L, R);
 77    else{
 78        ll ans = hsh(ls, l, mid, L, mid);
 79        ans += hsh(rs, mid + 1, r, mid + 1, R) * po[mid - L + 1] % mod;
 80        ans %= mod;
 81        return ans;
 82    }
 83}
 84
 85int main(){
 86//   Fastin;
 87    po[0] = 1;
 88    for(int i = 1; i < maxn; i++) po[i] = po[i - 1] * 1ll * pp % mod;
 89    pre[0] = 1;
 90    for(int i = 1; i < maxn; i++) pre[i] = pre[i - 1] + po[i], pre[i] %= mod;
 91    
 92    int n, m; scanf("%d %d", &n, &m);
 93    for(int i = 1; i <= n; i++){
 94        scanf("%d", a + i);
 95        build(1, 1, n, i, a[i]);
 96    }
 97    
 98    while(m--){
 99        int op; scanf("%d", &op);
100        if(op == 1){
101            int l, r; scanf("%d %d", &l, &r);
102            update(1, 1, n, l, r);
103        }
104        else{
105            int l, r, k; scanf("%d %d %d", &l, &r, &k);
106            purify(1, 1, n);
107            
108            ll res1 = hsh(1, 1, n, l, l + k - 1);
109            ll res2 = hsh(1, 1, n, r, r + k - 1);
110            puts(res1 == res2? "yes": "no");
111        }
112    }
113
114    return 0;
115}

L 分组背包 & LCM

给出n,构造某种拆分$p_1+p_2+…+p_k = n$,使得$lcm(p_1, p_2, …, p_k)$最大。

可以证明,当${p_i}$两两互素时,其lcm一定最大:

对于任意一组拆分,如果存在$gcd(a, b) = d>=2$,那么可以通过提取出d得到一个新的拆分方法,使得lcm不变,但是元素和变小。

另外,如果存在某个元素是若干素因子的乘积,也可以通过拆分该元素成若干的小数字,使得lcm不变、元素和变小。

通过上述两种更新得到后的新序列的情况一定不劣于原序列,那么不断进行这样的操作,最终一定会得到两两互素的序列。(这一点是显然的,因为上述两种操作均是单向的,且不可能构成环)

这样即可说明,两两互素的情况一定不会劣于任意的其他拆分方法。

那么分组背包,对素数进行分组,随便泡一下就过了。

cpp
 1
 2const int maxn = 3e4 + 10;
 3int prime[maxn], top = 0, vis[maxn];
 4void euler(){
 5    for(int i = 2; i <= 30000; i++){
 6        if(!vis[i]) prime[top++] = i;
 7        for(int j = 0; j < top && prime[j] * i <= 30000; j++){
 8            vis[prime[j] * i] = 1;
 9            if(i % prime[j] == 0) break;
10        }
11    }
12}
13double dp[maxn], w[maxn];
14
15int main(){
16    // Fastin;
17
18    euler();
19    for(int i = 1; i <= 30000; i++) w[i] = log(i);
20
21    //遍历所有的组
22    for(int i = 0; i < top; i++){
23        for(int n = 30000; n >= 1; n--){
24            ll p = prime[i];
25            //用给定组中的所有物品来尝试更新当前重量。
26            while(p <= n){
27                dp[n] = max(dp[n], dp[n - p] + w[p]);
28                p *= prime[i];
29            }
30        }
31    }
32
33    TTT{
34        int n; scanf("%d", &n); printf("%.9f\n", dp[n]);
35    }
36    return 0;
37}
嗨! 这里是 rqdmap 的个人博客, 我正关注 GNU/Linux 桌面系统, Linux 内核 以及一切有趣的计算机技术! 希望我的内容能对你有所帮助~
如果你遇到了任何问题, 包括但不限于: 博客内容说明不清楚或错误; 样式版面混乱; 加密博客访问请求等问题, 请通过邮箱 rqdmap@gmail.com 联系我!
修改日志
  • 2023-09-01 18:14:49 单独划分ACM专题; 移动部分博客进入黑洞归档
  • 2023-05-29 23:05:14 博客结构与操作脚本重构
  • 2023-05-08 21:44:36 博客架构修改升级
  • 2022-11-16 01:27:34 迁移老博客文章内容