Luogu5072 [Ynoi2015]盼君勿忘 【莫队】

题目描述:对于一个长度为\(n\)的序列,\(m\)次询问\(l,r,p\),计算\([l,r]\)的所有子序列的不同数之和\(\mathrm{mod} \ p\)

数据范围:\(n,m,a_i\leq 10^5,p\leq 10^9\)

来做做Ynoi中相对简单的题目。。。

首先我们考虑每个数的贡献,如果它出现了\(k\)次,那么会在\(2^{r-l+1}-2^{r-l+1-k}\)个子序列中出现。所以维护\(s[k]\)表示所有出现\(k\)次的数之和,而且\(s[k]\)中不为0的只有\(\sqrt{n}\)个。

所以使用莫队,维护\(s[k]\)并使用hash表维护\(s[k]\)中不为0的个数,并使用光速幂预处理\(2\)的幂次,然后可以\(O(\sqrt{n})\)计算了,时间复杂度\(O((n+m)\sqrt{n})\)

如果你就这样写了,很容易被卡常,但是根据lxl的数据,hash表可以只维护出现次数\(>\sqrt{n}\)的,然后\([1,\sqrt{n}]\)的直接遍历,这样常数就会小很多。

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 100003, mod = 998244353;
inline int kasumi(int a, int b){
    int res = 1;
    while(b){
        if(b & 1) res = (LL) res * a % mod;
        a = (LL) a * a % mod; b >>= 1;
    }
    return res;
}
int n, m, inv[N], root[N << 2], val[N * 350], ls[N * 350], rs[N * 350], cnt;
inline int Add(int a, int b){return (a + b >= mod) ? (a + b - mod) : (a + b);}
inline int Sub(int a, int b){return (a < b) ? (a + mod - b) : (a - b);}
inline int add(int a, int b){return Add((LL) a * Sub(1, b) % mod, (LL) b * Sub(1, a) % mod);}
inline void change(int &x, int L, int R, int l, int r, int v){
    if(!x) x = ++ cnt;
    if(l <= L && R <= r){val[x] = add(val[x], v); return;}
    int mid = L + R >> 1;
    if(l <= mid) change(ls[x], L, mid, l, r, v);
    if(mid < r) change(rs[x], mid + 1, R, l, r, v);
}
inline int query(int x, int L, int R, int p){
    if(!x) return 0;
    if(L == R) return val[x];
    int mid = L + R >> 1;
    if(p <= mid) return add(val[x], query(ls[x], L, mid, p));
    else return add(val[x], query(rs[x], mid + 1, R, p));
}
inline void change(int x, int L, int R, int l1, int r1, int l2, int r2, int v){
    if(l1 <= L && R <= r1){change(root[x], 1, n, l2, r2, v); return;}
    int mid = L + R >> 1;
    if(l1 <= mid) change(x << 1, L, mid, l1, r1, l2, r2, v);
    if(mid < r1) change(x << 1 | 1, mid + 1, R, l1, r1, l2, r2, v);
}
inline int query(int x, int L, int R, int p1, int p2){
    if(L == R) return query(root[x], 1, n, p2);
    int mid = L + R >> 1;
    if(p1 <= mid) return add(query(root[x], 1, n, p2), query(x << 1, L, mid, p1, p2));
    else return add(query(root[x], 1, n, p2), query(x << 1 | 1, mid + 1, R, p1, p2));
}
int main(){
    scanf("%d%d", &n, &m);
    for(Rint i = 1;i <= n;i ++) inv[i] = kasumi(i, mod - 2);
    while(m --){
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if(opt == 1){
            if(l > 1){
                change(root[0], 1, n, 1, l - 1, 1);
                change(1, 1, n, 1, l - 1, l, r, inv[r - l + 1]);
            }
            if(r < n){
                change(root[0], 1, n, r + 1, n, 1);
                change(1, 1, n, l, r, r + 1, n, inv[r - l + 1]);
            }
            if(l < r) change(1, 1, n, l, r, l, r, 2ll * inv[r - l + 1] % mod);
            change(root[0], 1, n, l, r, Sub(1, inv[r - l + 1]));
        } else if(l == 1) printf("%d\n", Sub(1, query(root[0], 1, n, r)));
        else printf("%d\n", Sub(1, query(1, 1, n, l - 1, r)));
    }
}

转载于:https://www.cnblogs.com/AThousandMoons/p/11558994.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值