莫队练习




HDU-5145 NPY and girls [莫队][组合数][逆元]

HDU-5145 NPY and girls

题意

每个女孩所在的班级为 a [ i ] a[i] a[i],NPY每次到一个班级只能拜访一个女孩,问NPY访问区间 [ l , r ] [l,r] [l,r] 里的女孩的访问方式总共有多少种

思路

NPY访问总次数应为 r − l + 1 r-l+1 rl+1,对于每个女孩,就是在这几个位置中任意选择一个位置,即求组合数
对于在 x x x 班级的女孩,访问的次数为 C r − l + 1 c n t [ x ] C_{r-l+1}^{cnt[x]} Crl+1cnt[x]
则将所有的都乘起来,即结果
由于组合数 C n + 1 m + 1 = C n m ∗ n + 1 m + 1 C_{n+1}^{m+1} = C_{n}^{m}*\frac{n+1}{m+1} Cn+1m+1=Cnmm+1n+1
l e n = r − l + 1 len = r-l+1 len=rl+1,即
x x x 班级的人数增加 1 1 1 p r e pre pre 为未增加前的结果,则现在的结果应为
r e s = p r e ∗ l e n ∗ 1 c n t [ i ] res = pre * len * \frac{1}{cnt[i]} res=prelencnt[i]1
x x x 班级的人数减少 1 1 1 p r e pre pre 为未减少前的结果,则现在的结果应为
r e s = p r e ∗ c n t [ x ] ∗ 1 l e n res = pre * cnt[x] * \frac{1}{len} res=precnt[x]len1
利用莫队分块转移区间, O ( 1 ) O(1) O(1) 求得每次转移后的结果

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int maxn = 3e4 + 5;;
const ll mod = 1e9 + 7;

int block;
struct node{
    int l, r, id;
    bool operator<(const node &_) const {
        return l/block == _.l/block ? r < _.r : l/block < _.l/block;
    }
}q[maxn];
int a[maxn], res[maxn], cnt[maxn];
ll inv[maxn];
ll ans;

void init() {
    inv[0] = inv[1] = 1;
    for(int i = 2; i < maxn; i++) 
        inv[i] = (mod-mod/i) * inv[mod%i] % mod;
}

void add(ll num, int x) {
    cnt[a[x]]++;
    ans = 1ll * ans * inv[cnt[a[x]]] % mod * num % mod;
}

void del(ll num, int x) {
    ans = 1ll * ans * cnt[a[x]] % mod * inv[num] % mod;
    cnt[a[x]]--;
}
 
int main() {
    int T; init();
    scanf("%d", &T);
    while(T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        for(int i = 1; i <= m; ++i) {
            scanf("%d%d", &q[i].l, &q[i].r); q[i].id = i;
        }
        block = sqrt(n + 0.0);
        sort(q + 1, q + 1 + m);
        memset(cnt, 0, sizeof(cnt));
        int L = 1, R = 0; ll num = 0; ans = 1; 
        for(int i = 1; i <= m; ++i) {
            int l = q[i].l, r = q[i].r;
            while(L > l)    add(++num, --L);
            while(R < r)    add(++num, ++R);
            while(L < l)    del(num--, L++);
            while(R > r)    del(num--, R--);
            res[q[i].id] = ans;
        }
        for(int i = 1; i <= m; ++i) printf("%d\n", res[i]);
    }
    return 0;
}


SPOJ-COT2 Count on a tree II [树上莫队]

SPOJ-COT2 Count on a tree II

题意

给一棵树,问点 ( u , v ) (u, v) (u,v) 的简单路径上,有多少个不同的点值

思路

建议看洛谷题解 SP10707 COT2 - Count on a tree II 题解
第一篇挺容易理解的
就是将树转为线性结构

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;;
const ll mod = 1e9 + 7;
const int MAX_LOG = 20;

int block;
struct node{
    int l, r, lc, id;
    bool operator<(const node &_) const {
        return l/block == _.l/block ? r < _.r : l/block < _.l/block;
    }
}q[maxn];
vector<int> ed[maxn];
vector<int> vv;
int fa[maxn][40], dep[maxn], dfn[maxn];
int res[maxn], cnt[maxn];
int st[maxn], en[maxn]; // 欧拉序
int vis[maxn]; // 是否在区间中
int a[maxn];
ll ans;
int tol;

int getid(int x) {
    return lower_bound(vv.begin(), vv.end(), x) - vv.begin() + 1;
}

void init(int n) {
    vv.clear(); tol = 0;
    memset(cnt, 0, sizeof(cnt));
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= n; ++i) ed[i].clear();
}

void deal(int x) {
    if(vis[x] && --cnt[a[x]] == 0) ans--;
    else if(!vis[x] && cnt[a[x]]++ == 0) ans++;
    vis[x] ^= 1; // 是否在已计算的区间内
}

void dfs(int u, int pre, int d) {
    dep[u] = d, st[u] = ++tol;
    dfn[tol] = u, fa[u][0] = pre;
    for(int i = 1; (1<<i) <= dep[u]; ++i)
        fa[u][i] = fa[fa[u][i-1]][i-1];
    for(auto v : ed[u]) {
        if(v == pre) continue;
        dfs(v, u, d + 1);
    }
    en[u] = ++tol;
    dfn[tol] = u;
}

int lca(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    int x = dep[u] - dep[v];
    for(int i = 0; i <= MAX_LOG; ++i)
        if((x >> i) & 1) u=fa[u][i];
    if(u == v) return u;
    for(int i = MAX_LOG; i >= 0; --i) {
        if(fa[u][i] != fa[v][i])
            u = fa[u][i], v = fa[v][i];
    }
    return fa[u][0];
}

void solve(int n, int m) {
    dfs(1, 0, 1);
    block = sqrt(2*n + 0.0); // 点个数 2n
    for(int i = 1, u, v; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        int lc = lca(u, v);
        if(st[u] > st[v]) swap(u, v);
        q[i].id = i, q[i].lc = lc;
        if(lc == u) q[i].l=st[u], q[i].r=st[v]; // 同一条链上
        else        q[i].l=en[u], q[i].r=st[v]; // 不同链上
    }
    sort(q + 1, q + 1 + m);
    sort(vv.begin(), vv.end());
    vv.erase(unique(vv.begin(), vv.end()), vv.end());
    for(int i = 1; i <= n; ++i) a[i] = getid(a[i]);
    int L = q[1].l, R = q[1].l-1;
    for(int i = 1; i <= m; ++i) {
        int l = q[i].l, r = q[i].r;
        while(L > l)    deal(dfn[--L]);
        while(R < r)    deal(dfn[++R]);
        while(L < l)    deal(dfn[L++]);
        while(R > r)    deal(dfn[R--]);
        res[q[i].id] = ans;
        if(cnt[a[q[i].lc]] == 0)    res[q[i].id]++; // 算上lca的
    }
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m); init(n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]); vv.push_back(a[i]);
    }
    for(int i = 1, u, v; i < n; ++i) {
        scanf("%d%d", &u, &v);
        ed[u].push_back(v), ed[v].push_back(u);
    }
    solve(n, m);
    for(int i = 1; i <= m; ++i) printf("%d\n", res[i]);
    return 0;
}


NBUT-1457 Sona [莫队]

NBUT-1457 Sona

题意

求区间 [ l , r ] [l, r] [l,r] 中, ∑ c n t [ x ] 3 ( x ∈ { a [ j ] ∣ l ≤ j ≤ r } ) \sum cnt[x]^3(x \in \{a[j] | l \leq j \leq r\}) cnt[x]3(x{a[j]ljr}) c n t [ x ] cnt[x] cnt[x] 表示 x x x 的个数

思路

题意读懂的话就是简单的莫队模板,稍微改一下
(其实我也没读题,看样例猜的
先扣除当前的 c n t [ x ] cnt[x] cnt[x] 的贡献,再加上更改后的贡献
这题时间卡的有点紧,如果超时可以加加看快读,都开int然后再取模

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;

int block;
struct node{
    int l, r, id;
    bool operator<(const node &_) const {
        return l/block == _.l/block ? r < _.r : l/block < _.l/block;
    }
}q[maxn];
vector<int> vv;
ll res[maxn], cnt[maxn];
int a[maxn];
ll ans;

inline int getid(int x) {
    return lower_bound(vv.begin(), vv.end(), x) - vv.begin() + 1;
}

ll cal(ll x) {
    return x * x * x;
}

void add(int x) {
    ans -= cal(cnt[a[x]]);
    cnt[a[x]] += 1;
    ans += cal(cnt[a[x]]);
}

void del(int x) {
    ans -= cal(cnt[a[x]]);
    cnt[a[x]] -= 1;
    ans += cal(cnt[a[x]]);
}

void solve(int n, int m) {
    block = sqrt(n + 0.0); 
    sort(q + 1, q + 1 + m);
    sort(vv.begin(), vv.end());
    vv.erase(unique(vv.begin(), vv.end()), vv.end());
    for(int i = 1; i <= n; ++i) a[i] = getid(a[i]);
    int L = q[1].l, R = q[1].l-1;
    for(int i = 1; i <= m; ++i) {
        int l = q[i].l, r = q[i].r;
        while(L > l)    add(--L);
        while(R < r)    add(++R);
        while(L < l)    del(L++);
        while(R > r)    del(R--);
        res[q[i].id] = ans;
    }
}

int main() {
    int n, m;
    while(~scanf("%d", &n)) {
        vv.clear(); ans = 0;
        for(int i = 1; i <= n; ++i) cnt[i]=0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]); vv.push_back(a[i]);
        }
        scanf("%d", &m);
        for(int i = 1; i <= m; ++i) {
            scanf("%d%d", &q[i].l, &q[i].r); q[i].id = i;
        }
        solve(n, m);
        for(int i = 1; i <= m; ++i) printf("%lld\n", res[i]);
    }
    return 0;
}


HDU-5213 Lucky [莫队][容斥]

HDU-5213 Lucky

题意

每次询问给两个区间 [ l , r ] , [ u , v ] [l, r], [u, v] [l,r],[u,v]
x ∈ [ l , r ] , y ∈ [ u , v ] , x + y = = k x \in [l, r], y \in [u, v], x+y == k x[l,r],y[u,v],x+y==k,问这样的对数存在多少对, ( l ≤ r < u ≤ v ) (l \leq r < u \leq v) (lr<uv)
比如 [1 2 1 2], k = 3 k = 3 k=3
[ l , r ] = [ 1 , 3 ] , [ u , v ] = [ 2 , 4 ] [l,r]=[1,3], [u, v]=[2, 4] [l,r]=[1,3],[u,v]=[2,4]
存在的对有(这里写下标,看起来比较清晰) ( 1 , 2 ) ( 1 , 4 ) ( 2 , 3 ) ( 3 , 2 ) ( 3 , 4 ) (1, 2)(1,4)(2,3)(3,2)(3,4) (1,2)(1,4)(2,3)(3,2)(3,4)

思路

考虑莫队,但莫队每次计算的都是一个区间内的组合,那么将这两个区间先合并起来看
区间 [ l , v ] [l, v] [l,v] 这段的值可以看做以下几个部分组成
在这里插入图片描述
其实也就是容斥求解

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn = 3e4 + 5;
const ll mod = 1e9 + 7;

int block;
struct node{
    int l, r, id, op;
    bool operator<(const node &_) const {
        return l/block == _.l/block ? r < _.r : l/block < _.l/block;
    }
}q[maxn<<2];
int cnt[maxn<<2];
int a[maxn];
ll res[maxn];
ll ans;
int k;


void add(int x) {
    if(x >= k)  return ;
    ans -= cnt[x] * cnt[k-x];
    cnt[x]++;
    ans += cnt[x] * cnt[k-x];
}

void del(int x) {
    if(x >= k)  return ;
    ans -= cnt[x] * cnt[k-x];
    cnt[x]--;
    ans += cnt[x] * cnt[k-x];
}

void solve(int n, int m) {
    block = sqrt(n + 0.0); 
    sort(q + 1, q + 1 + m);
    int L = q[1].l, R = q[1].l-1;
    for(int i = 1; i <= m; ++i) {
        int l = q[i].l, r = q[i].r;
        while(L > l)    add(a[--L]);
        while(R < r)    add(a[++R]);
        while(L < l)    del(a[L++]);
        while(R > r)    del(a[R--]);
        res[q[i].id] += ans * q[i].op;
    }
}

int main() {
    int n, m;
    while(~scanf("%d", &n)) {
        scanf("%d", &k);
        for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        scanf("%d", &m);
        for(int i = 1, j = 1; i <= m; i++) {
            int l, r, u, v;
            scanf("%d%d%d%d", &l, &r, &u, &v);
            q[j].l = l, q[j].r = v, q[j].id = i, q[j].op = 1; j++;
            q[j].l = l, q[j].r = u-1, q[j].id = i, q[j].op = -1; j++;
            q[j].l = r+1, q[j].r = v, q[j].id = i, q[j].op = -1; j++;
            q[j].l = r+1, q[j].r = u-1, q[j].id = i, q[j].op = 1; j++;
        }
        ans = 0;
        memset(cnt, 0, sizeof(cnt));
        memset(res, 0, sizeof(res));
        solve(n, m*4);
        for(int i = 1; i <= m; ++i) printf("%lld\n", res[i]);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值