数据结构速写

vj的数据结构速写
不挂题目,只放vj的题目顺序
代码删去了头

A

区间\(lcm\)
这个东西首先可以表示成\(\prod_{p \in prime}{p^{maxcnt_{i,p}}}\),其中\(cnt_{i,p}\)\(a_i\)\(p\)的幂次
先对每个数进行质因数分解,然后在他的位置上乘上每个质因子,如果这个质因子的幂次在前面出现过,在最后一个位置乘上逆元
然后这个就是主席树了
分解质因数是一个\(\log\),主席树一个\(\log\)
时间复杂度是\(O(n\log^2a+m\log n)\),空间复杂度是\(O(n\log^2a)\)

A
int n, root[M], pos[M], inv[M], f[M], tot;
struct tree {
    int l, r, sum;
    #define ls e[rt].l
    #define rs e[rt].r
}e[M * 200];
inline void pushup(int rt) { e[rt].sum = 1ll * e[ls].sum * e[rs].sum % mod; }
int build(int l, int r) {
    int rt = ++tot; e[rt].sum = 1;
    if (l == r) return rt;
    int mid = l + r >> 1;
    ls = build(l, mid), rs = build(mid + 1, r);
    return rt;
}
int updata(int frt, int l, int r, int pos, ll val) {
    int rt = ++tot; e[rt] = e[frt];
    if (l == r) { e[rt].sum = 1ll * e[rt].sum * val % mod; return rt; }
    int mid = l + r >> 1;
    if (pos <= mid) ls = updata(e[frt].l, l, mid, pos, val);
    else rs = updata(e[frt].r, mid + 1, r, pos, val);
    pushup(rt); return rt;
}
int query(int rt, int l, int r, int L) {
    if (l >= L) return e[rt].sum;
    if (r < L) return 1;
    int mid = l + r >> 1;
    return 1ll * query(ls, l, mid, L) * query(rs, mid + 1, r, L) % mod;
}

WWW main() {
    n = read(); inv[1] = 1; e[0].sum = 1;
    rep(i, 2, 200000) {
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
        if (!f[i]) for (int j = i; j <= 200000; j += i) f[j] = i;
    }
    rep(i, 1, n) {
        int x = read(); root[i] = root[i - 1];
        while(f[x]) {
            int p = f[x], k = 1;
            while(!(x % p)) {
                x /= p; k *= p;
                if (pos[k]) root[i] = updata(root[i], 1, n, pos[k], inv[p]);
                pos[k] = i;
            }
            root[i] = updata(root[i], 1, n, i, k);
        }
    }
    int T = read(); ll lastans = 0;
    while(T--) {
        ll l = ((ll)read() + lastans) % n + 1, r = ((ll)read() + lastans) % n + 1;
        if (l > r) swap(l, r);
        print(lastans = query(root[r], 1, n, l), 1);
    }
    return 0;
}

B

\(Kruscal\)\(Kruscal重构树\) 不是一个东西....
由于按照编号顺序连边,所以每个边的权值就是其编号大小
性质:原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值
所以我们跑一棵重构树出来,然后两点之间的\(LCA\)的权值就是两点连通时间
判断\([l,r]\)这些点连通可以直接判断\(i\)\(i+1\)是否连通,设\(f_i\)\(i\)\(i+1\)的连通时间,则答案就是\(maxf_i,i \in [l,r)\)
所以预处理出所有的\(f\)放进线段树里,直接查询即可
重构树是一个\(O(m \log m)\),预处理\(f\)\(O(n \log n)\),总时间复杂度是\(O(m \log m+n \log n+q \log n)\)
注意重构树的节点是\(2 \times n-1\)个,所以一些地方需要二倍数组
当你RE的时候,注释一部分代码调试固然是好的,但是多测题你连整组数据都读不完你调J呢

B
int n, m, q, val[M << 1];
struct bcj {
    int f[M << 1];
    void init(int n) { rep(i, 1, n) f[i] = i; }
    inline int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
    inline void un(int x, int y) { f[find(y)] = find(x); }
}F;
struct Gragh {
    struct edge { int v, next; }e[M << 2]; 
    int head[M << 1], tot = 1;
    void add(int u, int v) { e[tot].v = v, e[tot].next = head[u], head[u] = tot++; }
}E;
struct edge { int u, v; }e[M << 1];
void kruscal() {
    int res = n; F.init(2 * n - 1);
    rep(i, 1, m) {
        if (F.find(e[i].u) != F.find(e[i].v)) {
            int p = ++res, u = F.find(e[i].u), v = F.find(e[i].v);
            val[p] = i;
            E.add(u, p), E.add(v, p), E.add(p, u), E.add(p, v);
            F.un(p, u), F.un(p, v); 
            if (res == 2 * n - 1) return;
        }
    }
}
int f[M << 1][20], dep[M << 1];
void dfs(int x, int fa) {
    f[x][0] = fa; dep[x] = dep[fa] + 1;
    rep(i, 1, 17) f[x][i] = f[f[x][i - 1]][i - 1];
    for (int i = E.head[x]; i; i = E.e[i].next) {
        int v = E.e[i].v;
        if (v == fa) continue;
        dfs(v, x);
    }
}
int LCA(int u, int v) {
    if (dep[u] < dep[v]) swap(u, v);
    dwn(i, 17, 0) if (dep[f[u][i]] >= dep[v]) u = f[u][i];
    if (u == v) return u;
    dwn(i, 17, 0) if (f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
    return f[u][0];
}
int maxn[M << 2];
#define ls (rt << 1)
#define rs ((rt << 1) | 1)
inline void pushup(int rt) { maxn[rt] = Max(maxn[ls], maxn[rs]); }
inline void build(int rt, int l, int r) {
    if (l == r) { maxn[rt] = val[LCA(l, l + 1)]; return; }
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    pushup(rt);
}
inline int query(int rt, int l, int r, int L, int R) {
    if (L > R) return 0;
    if (L <= l and r <= R) return maxn[rt];
    int mid = l + r >> 1, maxn = 0;
    if (L <= mid) maxn = query(ls, l, mid, L, R);
    if (R > mid) maxn = Max(maxn, query(rs, mid + 1, r, L, R));
    return maxn;
}
void qk() { memset(E.head, 0, sizeof(E.head)), E.tot = 1;}
WWW main() {
    int T = read();
    while(T--) {
        n = read(), m = read(), q = read();
        rep(i, 1, m) { e[i].u = read(), e[i].v = read(); }
        kruscal(); 
        dfs(F.find(1), 0);
        build(1, 1, n - 1);
        while(q--) {
            int l = read(), r = read() - 1;
            print(query(1, 1, n - 1, l, r), 0);
        }
        putchar('\n');
        if (T) qk();
    }
    return 0;
}

C

思路明白之后很简单,实现很J
先考虑一个人作为组长时组员最大数量,实际上就是满足\(a\in[a_i-k,a_i+k]\)\(r \le r_i\)的所有人
按照年龄排序,维护双指针,将\(r\)离散化插入树状数组即可预处理
将所有询问离线,对于一组\((x,y)(a_x < a_y)\),能作为他们组长的人需要满足\(a\in[a_x+k, a_y-k],r \ge max(r_x, r_y)\),所以直接按照\(max(r_x,r_y)\)降序排列,然后将满足第二个条件的插入权值线段树,直接区间查询最大值即可
线段树如果不想离散化就记得动态开点
时间复杂度是\(O((n+m) \log n)\)
写线段树还是要注意值域和左右边界的问题,开码之前先理清楚思路,想想怎么写,开什么数组,不然打到一半你就打不下去了

C
const int maxn = 1000000000;
int n, k, m, rt, a[M], r[M], lsh[M], mz[M], ans[M], tot;
void dir(int a[], int n) {
    rep(i, 1, n) lsh[i] = a[i];
    sort(lsh + 1, lsh + 1 + n);
    int cnt = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
    rep(i, 1, n) a[i] = lower_bound(lsh + 1, lsh + 1 + n, a[i]) - lsh;
} 
paya tl[M];
struct BIT {
    int c[M];
    #define low_bit (x & -x)
    inline void updata(int x, int val) { while(x <= n) c[x] += val, x += low_bit; }
    inline int query(int x) { int res = 0; while(x) res += c[x], x -= low_bit; return res;  }
}C;
inline bool operator< (const paya &A, const paya &B) { return A.fr < B.fr; }
struct tree { int l, r, maxn; }e[M << 5];
#define ls e[rt].l
#define rs e[rt].r
inline void pushup(int rt) { e[rt].maxn = Max(e[ls].maxn, e[rs].maxn); }
void updata(int &rt, int l, int r, int pos, int val) {
    if (!rt) rt = ++tot;
    if (l == r) { e[rt].maxn = Max(e[rt].maxn, val); return; }
    int mid = l + r >> 1;
    if (pos <= mid) updata(ls, l, mid, pos, val);
    else updata(rs, mid + 1, r, pos, val);
    pushup(rt);
}
int query(int rt, int l, int r, int L, int R) {
    if (L > R or !rt) return -1;
    if (L <= l and r <= R) return e[rt].maxn;
    int mid = l + r >> 1, maxn = -1;
    if (L <= mid) maxn = query(ls, l, mid, L, R);
    if (R > mid) maxn = Max(maxn, query(rs, mid + 1, r, L, R));
    return maxn;
}
struct query{ 
    int id, x, y;
    friend bool operator< (const query &A, const query &B) { return Max(r[A.x], r[A.y]) > Max(r[B.x], r[B.y]); }
}q[M];

WWW main() {
    n = read(), k = read();
    rep(i, 1, n) r[i] = read(); dir(r, n);
    rep(i, 1, n) a[i] = read();
    rep(i, 1, n) tl[i] = papaya(a[i], i);
    sort(tl + 1, tl + 1 + n);
    int st = 1, ed = 0;
    rep(i, 1, n) {
        while(ed < n and tl[ed + 1].fr - tl[i].fr <= k) C.updata(r[tl[++ed].sc], 1);
        while(st < n and tl[i].fr - tl[st].fr > k) C.updata(r[tl[st++].sc], -1);
        mz[tl[i].sc] = C.query(r[tl[i].sc]);
    }
    rep(i, 1, n) tl[i] = papaya(r[i], i);
    sort(tl + 1, tl + 1 + n);
    m = read();
    rep(i, 1, m) q[i].id = i, q[i].x = read(), q[i].y = read();
    sort(q + 1, q + 1 + m); int now = n + 1;
    rep(i, 1, m) {
        if (a[q[i].x] > a[q[i].y]) swap(q[i].x, q[i].y);
        int f = Max(r[q[i].x], r[q[i].y]);
        while(now > 1 and tl[now - 1].fr >= f) now--, updata(rt, 0, maxn, a[tl[now].sc], mz[tl[now].sc]);
        ans[q[i].id] = query(rt, 0, maxn, Max(0, a[q[i].y] - k), Min(maxn, a[q[i].x] + k));
    }
    rep(i, 1, m) print(ans[i], 1);
    return 0;
}

D

求最小删除就改成最大保留
由于会和异或起来最小的连边,又要求是一棵树,所以需要只剩一对\((i,j)\)互相连边
把所有数放进\(trie\)树,设\(f_x\)为节点\(x\)的最大保留数
如果只有一个儿子则直接继承,两个儿子就\(f_x=max(f_l, f_r)+1\)
意义就是保留一棵子树,在另一棵子树里保留一个点,这个点会和保留的那一棵子树内其中一个点连边
时间复杂度\(O(nlogn)\)

D
int n, a[M], tot = 1;
struct trie { int son[2], f; }e[M << 5];
#define ls e[rt].son[0]
#define rs e[rt].son[1]
#define kid e[rt].son[op]
void insert(int x) {
    int rt = 1;
    dwn(i, 30, 0) {
        bool op = x >> i & 1;
        if (!kid) kid = ++tot;
        rt = kid;
    }
    e[rt].f = 1;
}
void D(int rt) {
    if (!ls and !rs) return;
    if (!ls) { D(rs), e[rt].f = e[rs].f; return; }
    if (!rs) { D(ls), e[rt].f = e[ls].f; return; }
    D(ls), D(rs);
    e[rt].f = Max(e[ls].f, e[rs].f) + 1;
}

WWW main() {
    n = read(); 
    rep(i, 1, n) insert(read());
    D(1);
    print(n - e[1].f, 0);
    return 0;
}

E

没做,咕咕咕

F

平衡树?狗都不打
暴力分块,每个块维护一个\(deque\)和桶即可
可以用\(deque\)的内置函数\(emplace\)\(erase\)来实现两个端点的修改,也可以直接暴力改
然后判一下同块即可
复杂度应该\(O(n \sqrt n)\)

F
int n, a[M], st[N], ed[N], bl[M], t[N][M];
deque<int> dq[N], ls;
void fk() {
    int q = sqrt(n);
    rep(i, 1, q) {
        st[i] = ed[i - 1] + 1;
        ed[i] = n / q * i;
    }
    if (ed[q] != n) st[q + 1] = ed[q] + 1, ed[++q] = n;
    rep(i, 1, q) rep(j, st[i], ed[i]) {
        bl[j] = i;
        t[i][a[j]]++;
        dq[i].push_back(a[j]);
    }
}
void change(int l, int r) {
    int L = bl[l], R = bl[r];
    if (L == R) {
        l -= st[L], r -= st[L];
        int tmp = dq[L][r];
        dwn(i, r - 1, l) dq[L][i + 1] = dq[L][i];
        dq[L][l] = tmp;
        return;
    }
    l -= st[L], r -= st[R];
    int tmp = dq[R][r]; t[R][tmp]--;
    dwn(i, r, 1) dq[R][i] = dq[R][i - 1];
    dq[R][0] = dq[R - 1].back(); t[R][dq[R][0]]++;
    dwn(i, R - 1, L + 1) {
        t[i][dq[i].back()]--; dq[i].pop_back();
        dq[i].push_front(dq[i - 1].back());
        t[i][dq[i].front()]++;
    }
    t[L][dq[L].back()]--;
    dwn(i, ed[L] - st[L], l + 1) dq[L][i] = dq[L][i - 1];
    dq[L][l] = tmp; t[L][tmp]++;
}
int query(int l, int r, int k) {
    int res = 0, L = bl[l], R = bl[r];
    if (L == R) {
        l -= st[L], r -= st[L];
        rep(i, l, r) res += (dq[L][i] == k);
        return res;
    }
    rep(i, L + 1, R - 1) res += t[i][k];
    l -= st[L], r -= st[R];
    dwn(i, ed[L] - st[L], l) res += (dq[L][i] == k);
    rep(i, 0, r) res += (dq[R][i] == k);
    return res;
}

WWW main() {
    n = read();
    rep(i, 1, n) a[i] = read(); 
    fk();
    int T = read(), lastans = 0;
    while(T--) {
        int opt = read(), l = (read() + lastans - 1) % n + 1, r = (read() + lastans - 1) % n + 1;
        // print(l, 0), print(r, 1);
        if (l > r) swap(l, r);
        if (opt == 1) change(l, r);
        else print(lastans = query(l, r, (read() + lastans - 1) % n + 1), 1);
    }
    return 0;
}

G

主席树上的山海经
直接维护区间中区间的最小值似乎并不好维护,于是考虑二分答案
设二分到\(mid\),将原序列\(\ge mid\)的设为\(1\)\(<\)的设为\(0\)
此时问题就转化为了区间内最长连续\(1\)的长度是否\(\ge k\)
然后就将\(h_i\)离散化一下,主席树维护每个\(mid\)的情况
时间复杂度是\(O(n \log^2 n)\),空间复杂度是\(O(n \log n)\)
需要\(build\)将每个节点的\(len\)维护出来,不然合并的时候会有大问题(指WA on #6)

G
int n, a[M], lsh[M], root[M], cnt, tot;
vector<int> v[M];
struct mt_oc_J {
    int len, lx, rx, mx;
    mt_oc_J() { len = lx = rx = mx = 0; }
    friend mt_oc_J operator+ (const mt_oc_J &A, const mt_oc_J &B) {
        mt_oc_J C;
        C.len = A.len + B.len;
        C.lx = A.lx == A.len ? A.lx + B.lx : A.lx;
        C.rx = B.rx == B.len ? B.rx + A.rx : B.rx;
        C.mx = Max(Max(A.mx, B.mx), A.rx + B.lx);
        return C;
    }
};
struct tree { int l, r; mt_oc_J k; }e[M << 6];
#define ls e[rt].l
#define rs e[rt].r
int build(int rt, int l, int r) {
    rt = ++tot;
    if (l == r) { e[rt].k.len = 1; return rt; } 
    int mid = l + r >> 1;
    ls = build(ls, l, mid);
    rs = build(rs, mid + 1, r);
    e[rt].k = e[ls].k + e[rs].k; 
    return rt;
}
int updata(int frt, int l, int r, int pos) {
    int rt = ++tot; e[rt] = e[frt];
    if (l == r) { e[rt].k.lx = e[rt].k.rx = e[rt].k.mx = e[rt].k.len = 1; return rt; }
    int mid = l + r >> 1;
    if (pos <= mid) ls = updata(ls, l, mid, pos);
    else rs = updata(rs, mid + 1, r, pos);
    e[rt].k = e[ls].k + e[rs].k; return rt;
}
mt_oc_J query(int rt, int l, int r, int L, int R) {
    if (L <= l and r <= R) return e[rt].k;
    int mid = l + r >> 1;
    if (L > mid) return query(rs, mid + 1, r, L, R);
    if (R <= mid) return query(ls, l, mid, L, R);
    return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
}
int query(int L, int R, int k) {
    int l = 1, r = cnt;
    while(l < r) {
        int mid = l + r + 1 >> 1;
        if (query(root[mid], 1, n, L, R).mx >= k) l = mid;
        else r = mid - 1;
    }
    return l;
}
WWW main() {
    n = read();
    rep(i, 1, n) lsh[i] = a[i] = read();
    sort(lsh + 1, lsh + 1 + n);
    cnt = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
    rep(i, 1, n) a[i] = lower_bound(lsh + 1, lsh + 1 + cnt, a[i]) - lsh, v[a[i]].push_back(i);
    root[cnt + 1] = build(root[cnt + 1], 1, n);
    dwn(i, cnt, 1) {
        root[i] = root[i + 1];
        for (auto j : v[i]) root[i] = updata(root[i], 1, n, j);
    }
    int T = read();
    while(T--) {
        int l = read(), r = read(), k = read();
        print(lsh[query(l, r, k)], 1);
    }
    return 0;
}

H

特殊数据范围题
\(a \le 6\)\(lcm(1, 2, 3, 4, 5, 6) = 60\),所以我们可以直接将前面的时间\(x \bmod 60\)
线段树维护从\(l\)进入时为\(x\),经过\(l\)\(r\)这一段需要的时间
然后每次的\(pushup\)直接\(O(60)\)暴力即可
时间复杂度\(O(60n \log n)\)
查询\([l,r]\)这一段的时候,你返回的只是\([l,r]\)这一段的时间,你加上\(x\)的话显然就寄了

H
int n, f[M << 2][60], a[M];
#define ls (rt << 1)
#define rs ((rt << 1) | 1)
inline void pushup(int rt) { rep(i, 0, 59) f[rt][i] = f[ls][i] + f[rs][(i + f[ls][i]) % 60]; }
void build(int rt, int l, int r) {
    if (l == r) { rep(i, 0, 59) f[rt][i] = (i % a[l]) ? 1 : 2; return; }
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    pushup(rt);
}
void updata(int rt, int l, int r, int pos, int val) {
    if (l == r) { rep(i, 0, 59) f[rt][i] = (i % val) ? 1 : 2; return; }
    int mid = l + r >> 1;
    if (pos <= mid) updata(ls, l, mid, pos, val);
    else updata(rs, mid + 1, r, pos, val);
    pushup(rt);
} 
int query(int rt, int l, int r, int L, int R,int x) {
    if (L <= l and r <= R) return f[rt][x]; 
    int mid = l + r >> 1;
    if (L > mid) return query(rs, mid + 1, r, L, R, x);
    if (R <= mid) return query(ls, l, mid, L, R, x);
    int res = query(ls, l, mid, L, R, x);
    return res + query(rs, mid + 1, r, L, R, (res + x) % 60);
}

WWW main() {
    n = read();
    rep(i, 1, n) a[i] = read();
    build(1, 1, n);
    int T = read();
    while(T--) {
        char c;
        while(!isalpha(c = getchar()));
        int x = read(), y = read();
        if (c == 'C') updata(1, 1, n, x, y);
        else print(query(1, 1, n, x, y - 1, 0), 1);
    }
    return 0;
}

I

一上午的血压,终于过了
先考虑如何计算整个区间的贡献,我们来一个点一个点的考虑
对于每个点处理出\(l_i,r_i\)代表在这一段内\(a_i\)是最大值,所以包含\(a_i\)的合法区间的左右端点的范围就可以确定在\([l_i,i],[i,r_i]\)
我们枚举左端点或者右端点,以枚举右端点为例,设左端点的合法区间是\(l\in[L, R]\),固定左端点在\(i\),初始化\(L=l_i,R=l_i+1\)代表此时没有合法区间
一个重要的性质是排列,所以我们可以处理出每个数所在的位置,考虑枚举\(a_i\)的因数,只要\([l,r]\)能够包括一对因子即是合法区间
\(x \times y = a_i(x \ne y)\)\(l_i \le pos_x,pos_y \le i\),那么\(R = max(R, min(pos_x, pos_y))\)
处理完之后就扩张右端点,如果新的右端点是\(a_i\)的因数并且\(l_i \le pos_{\frac{a_i}{a_r}} \le r\),那么我可以再次尝试更新左端点
注意:\(pos_{\frac{a_i}{a_r}}\)是可以\(\ge i\)的,此时\(R\)应变成\(i\)而不是不更新,调这玩意至少调了两个小时,超
于是对于每个右端点\(r\)可以得到一个三元组\((L, R, r)\),代表以\(r\)为右端点时\(l\in[L,R]\)是合法的
然后就可以通过这种方式处理出所有的合法区间,用\(vector\)按照端点桶排
时间复杂度目前是\(O(n^2)\)
我们尽量让比较短的那一段作为枚举的点,这样似乎是启发式的,再加上枚举质因子的\(\log\),时间复杂度均摊\(O(n\log^2n)\)
将询问离线,然后按照将每个询问拆成两个,分别以左右端点桶排,用线段树维护贡献,操作是区间加区间求和
然后就结束了
(写完之后的痛骂,更不想写E了)

I
int n, m, a[M], pos[M], stk[M], top, l[M], r[M]; ll ans[M * 5];
vector<paya> vl[M], vr[M], Ql[M], Qr[M];
struct tree { ll sum, lz; }e[M << 2];
#define ls (rt << 1)
#define rs ((rt << 1) | 1)
inline void pushup(int rt) { e[rt].sum = e[ls].sum + e[rs].sum; }  
inline void change(int rt, int len, ll val) { e[rt].sum += val * len; e[rt].lz += val; }
inline void pushdown(int rt, int l, int r) {
    if (!e[rt].lz) return;
    int mid = l + r >> 1;
    change(ls, mid - l + 1, e[rt].lz), change(rs, r - mid, e[rt].lz);
    e[rt].lz = 0;
} 
void build(int rt, int l, int r) {
    e[rt].sum = e[rt].lz = 0;
    if (l == r) return;
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
}
void updata(int rt, int l, int r, int L, int R, ll val) {
    if (L <= l and r <= R) { change(rt, r - l + 1, val); return; } 
    pushdown(rt, l, r); int mid = l + r >> 1;
    if (L <= mid) updata(ls, l, mid, L, R, val);
    if (R > mid) updata(rs, mid + 1, r, L, R, val);
    pushup(rt);
}
ll query(int rt, int l, int r, int L, int R) {
    if (L <= l and r <= R) return e[rt].sum;
    pushdown(rt, l, r); int mid = l + r >> 1; ll res = 0;
    if (L <= mid) res += query(ls, l, mid, L, R);
    if (R > mid) res += query(rs, mid + 1, r, L, R);
    return res;
}

void workr(int x) {//l长
    int L = l[x], R = l[x] - 1;
    for (int i = 1; i * i < a[x]; i++) {
        if (a[x] % i) continue;
        int pos1 = pos[i], pos2 = pos[a[x] / i];
        if (Max(pos1, pos2) > x or Min(pos1, pos2) < l[x]) continue;
        R = Max(R, Min(pos1, pos2));
    }
    rep(i, x, r[x]) {
        if (!(a[x] % a[i])) {
            if (a[i] * a[i] == a[x]) continue;
            int pos1 = pos[a[x] / a[i]];
            if (pos1 >= l[x] and pos1 <= i) {
                R = Max(R, pos1);
                R = Min(R, x);
            }
        }
        if (R >= L) vr[i].push_back(py(L, R));
    }
}
void workl(int x) {//r长
    int L = r[x] + 1, R = r[x];
    for (int i = 1; i * i < a[x]; i++) {
        if (a[x] % i) continue;
        int pos1 = pos[i], pos2 = pos[a[x] / i];
        if (Max(pos1, pos2) > r[x] or Min(pos1, pos2) < x) continue;
        L = Min(L, Max(pos1, pos2));
    }
    dwn(i, x, l[x]) {
        if (!(a[x] % a[i])) {
            if (a[i] * a[i] == a[x]) continue;
            int pos1 = pos[a[x] / a[i]];
            if (pos1 <= r[x] and pos1 >= i) {
                L = Min(L, pos1);
                L = Max(L, x);
            }
        }
        if (L <= R) vl[i].push_back(py(L, R));
    }
}
void ot(int rt, int l, int r) {
    printf("%d %d %d %lld %lld\n", rt, l, r, e[rt].sum, e[rt].lz);
    if (l == r) return;
    int mid = l + r >> 1;
    ot(ls, l, mid), ot(rs, mid + 1, r);
}

WWW main() {
    n = read(), m = read();
    rep(i, 1, n) a[i] = read(), pos[a[i]] = i;
    rep(i, 1, n) {
        while(top and a[i] > a[stk[top]]) r[stk[top--]] = i - 1;
        l[i] = stk[top] + 1;
        stk[++top] = i;
    }
    while(top) r[stk[top]] = n, l[stk[top]] = stk[top - 1] + 1, top--;
    rep(i, 1, n) {
        if (r[i] - i <= i - l[i]) workr(i);
        else workl(i);
    }
    rep(i, 1, m) {
        int l = read(), r = read();
        Qr[r].push_back(py(l, i));
        Ql[l].push_back(py(r, i));
    }
    rep(i, 1, n) {
        for (auto j : vr[i]) updata(1, 1, n, j.fr, j.sc, 1);
        for (auto j : Qr[i]) ans[j.sc] += query(1, 1, n, j.fr, i);
    }
    build(1, 1, n);
    dwn(i, n, 1) {
        for (auto j : vl[i]) updata(1, 1, n, j.fr, j.sc, 1);
        for (auto j : Ql[i]) ans[j.sc] += query(1, 1, n, i, j.fr);
    }
    rep(i, 1, m) print(ans[i], 1);
    return 0;
}

J

有一个单调性的结论:答案一定存在每个点左右第一个\(w < w_i\)的点对中,答案来源从\(O(n^2)\)的量级到了\(O(n)\)
证明:
\(i<j<k,w_i < w_j < w_k\)
因为\(x\)单调递增,所以\((i, k)\)的答案一定不比\((i, j)\)优,所以\((i, k)\)一定不会对答案产生贡献,所以第一个之后的可以舍弃
又因为对左右都进行了处理,我们将每个点向左向右的理论最小值都挑了出来,所以答案一定在这些点对里
证毕
所以处理每个点左右第一个\(w \le w_i\)的点,用\(vector\)桶排(好套路,感觉写了一车这种处理了)
然后询问离线,按照右端点桶排(也是经典的套路)
线段树区间加,维护最小值即可,记得需要\(build\)把最小值初始化成极大值
结束

J
int n, m, x[M], w[M], l[M], r[M]; ll ans[M];
priority_queue<paya> q;
vector<int> v[M]; 
vector<paya> Q[M];
ll minx[M << 2];
#define ls (rt << 1)
#define rs ((rt << 1) | 1)
inline void pushup(int rt) { minx[rt] = Min(minx[ls], minx[rs]); } 
void build(int rt, int l, int r) {
    if (l == r) { minx[rt] = inf; return; }
    int mid = l + r >> 1;
    build(ls, l, mid), build(rs, mid + 1, r);
    pushup(rt);
}
void updata(int rt, int l, int r, int pos, ll val) {
    if (l == r) { minx[rt] = Min(minx[rt], val); return; }
    int mid = l + r >> 1;
    if (pos <= mid) updata(ls, l, mid, pos, val);
    else updata(rs, mid + 1, r, pos, val);
    pushup(rt);
}
ll query(int rt, int l, int r, int L, int R) {
    if (L <= l and r <= R) return minx[rt];
    int mid = l + r >> 1; ll mx = inf;
    if (L <= mid) mx = query(ls, l, mid, L, R);
    if (R > mid) mx = Min(mx, query(rs, mid + 1, r, L, R));
    return mx;
}

WWW main() {
    n = read(), m = read();
    rep(i, 1, n) x[i] = read(), w[i] = read();
    rep(i, 1, n) {
        while(!q.empty() and w[i] <= q.top().fr) r[q.top().sc] = i, q.pop();
        q.push(py(w[i], i));
    }
    while(!q.empty()) q.pop();
    dwn(i, n, 1) {
        while(!q.empty() and w[i] <= q.top().fr) l[q.top().sc] = i, q.pop();
        q.push(py(w[i], i));
    }
    build(1, 1, n);  
    rep(i, 1, n) if (r[i]) v[r[i]].push_back(i);
    rep(i, 1, m) {
        int L = read(), R = read();
        Q[R].push_back(py(L, i));
    }
    rep(i, 1, n) {
        if (l[i]) updata(1, 1, n, l[i], (ll)(x[i] - x[l[i]]) * ((ll)w[i] + w[l[i]]));
        for (auto j : v[i]) updata(1, 1, n, j, (ll)(x[i] - x[j]) * ((ll)w[i] + w[j]));
        for (auto j : Q[i]) ans[j.sc] = query(1, 1, n, j.fr, i);
    }
    rep(i, 1, m) print(ans[i], 1);
    return 0;
}
posted @ 2022-11-07 17:25  紫飨  阅读(41)  评论(10编辑  收藏  举报