闲话

好今日得到最让人感到奇妙的一件事是松鼠加入 ps_p live 力
虽然但是……比较地奇妙
……不知道该评价什么(
连步玎都不是 ps_p 的(

所以给了一天自由练习的时间就是刷数据结构的是吗(

然后看了一半的 Lycoris Recoil
强推!下次要看完(
今日推的原创番就是莉可丽丝了!

就先写到这罢
白鸟过河滩好听的!

数据结构……杂题?

这是五道题。明天似乎还有五道。
也是比较的水,所以题解写得短点(

CF1422F

给定长为 \(n\) 的序列 \(a\)\(q\) 次询问 \((l,r)\),你需要输出

\[\mathop{\operatorname{lcm}}\limits_{i=l}^r \{a_i\} \]

\(10^9 + 7\) 取模的值。强制在线。

\(1\le n, q\le 10^5, 1\le a_i \le 2\times 10^5\)

Muel的常数怎么那么小啊啊啊啊

考虑将每个数质因子分解。对每个 \(n\)\(O(\log n)\) 个状态。注意这里的状态不一定是质因子 \(p\) 和其最大指数的组合,非最大指数也可以。我们发现,我们需要的是 \([l,r]\) 区间内元素对应 \(p^k\) 状态中满足 \(k\) 最大的状态的积。
考虑对每个元素维护一个前缀版本,当一个元素可以贡献 \(p^k\) 时将其前面所有可以贡献 \(p^k\) 的元素的贡献删除。容易发现可以让每个元素只删除直接前驱做到这一点。
对每个贡献开桶,维护存在该贡献的直接前驱。当出现新的元素可以产生贡献时,在该元素版本中删除前驱并加入当前节点。这点可以使用主席树,根维开在下标上维护前缀版本,值维同样开在下标上维护某个前缀版本中特定位置的信息。

时空复杂度 \(O(n\log^2 n)\)

通过对质因子进行根号分治也可以做到。空间复杂度有些问题。

code
#include <bits/stdc++.h>
using namespace std;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 10, mod = 1e9 + 7;
int n, q, a[N], mx, l, r, ret, inv[N], lpf[N], pref[N];

struct SegmentCitrus {
    int rt[N], mlc;
    int & operator [] (const int & p) { return rt[p]; }
    const int operator [] (const int & p) const { return rt[p]; }

    struct node {
        int ls, rs, sum;
        #define ls(p) seg[p].ls
        #define rs(p) seg[p].rs
        #define sum(p) seg[p].sum
    } seg[N * 200];

    int add(int p, int l, int r, int pos, int val) {
        int ptr = ++mlc;
        if (l == r) {
            sum(ptr) = 1ll * sum(p) * val % mod;
            return ptr;
        } int mid = l + r >> 1;
        if (pos <= mid) ls(ptr) = add(ls(p), l, mid, pos, val), rs(ptr) = rs(p);
        else ls(ptr) = ls(p), rs(ptr) = add(rs(p), mid + 1, r, pos, val);
        sum(ptr) = 1ll * sum(ls(ptr)) * sum(rs(ptr)) % mod;
        return ptr;
    }
    void add(int id, int pos, int val) {
        rt[id] = add(rt[id], 1, n, pos, val);
    }

    int qry(int p, int l, int r, int bnd) {
        if (l >= bnd) return sum(p);
        if (r < bnd) return 1;
        int mid = l + r >> 1;
        return 1ll * qry(ls(p), l, mid, bnd) * qry(rs(p), mid + 1, r, bnd) % mod;
    }
    int qry(int l, int r) { return qry(rt[r], 1, n, l); }
} Tr;

signed main() {
    get(n); rep(i,1,n) get(a[i]), mx = max(mx, a[i]);

    inv[0] = inv[1] = 1;
    rep(i,2,mx) {
        inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
        if (!lpf[i]) for (int j = i; j <= mx; j += i) lpf[j] = i;
    }
    Tr.seg[0].sum = 1;
    rep(i,1,n) {
        int tmp = a[i]; Tr[i] = Tr[i-1];
        while (lpf[tmp]) {
            int k = lpf[tmp], t = 1;
            while (tmp % k == 0) {
                t *= k; tmp /= k;
                if (pref[t]) Tr.add(i, pref[t], inv[k]);
                pref[t] = i;
            } 
            Tr.add(i, i, t);
        }
    } 

    get(q); rep(i,1,q) {
        get(l, r); 
        l = (l + ret) % n + 1, r = (r + ret) % n + 1;
        if (l > r) swap(l, r);
        cout << (ret = Tr.qry(l, r)) << '\n';
    }
}


CF1706E

给出 \(n\) 个点, \(m\) 条边的不带权连通无向图, \(q\) 次询问至少要加完编号前多少的边,才能使得 \([l,r]\) 中的所有点两两连通。

\(1\le n, m, q \le 2\times 10^5\)

考虑一条边的边权为编号。记 \(f(u,v)\)\(u,v\) 在最小生成树上路径中边权最大值。容易发现答案即为

\[\max_{i=l}^{r-1} \{ f(i,i+1) \} \]

如果我们可以求得 \(f\),那就可以通过 st 表等结构快速得到答案。问题来到了如何求得 \(f\)
这是很容易的。使用 Kruskal 重构树可以做到 \(O(n\log n)\) 求解。

总时间复杂度 \(O(n\log n)\)

code
#include <bits/stdc++.h>
using namespace std;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e5 + 10;
int T, n, m, q, l, r;

int dsu[N], ele[N];
void init(int n) { rep(i,1,n) dsu[i] = ele[i] = i; } 
int find(int u) { return u == dsu[u] ? u : dsu[u] = find(dsu[u]); }

int dfn[N << 1], stp, dep[N << 1], a[N << 1], fa[N << 1], cnt, f[N], lgv[N << 1], st[20][N << 1];
vector <int> g[N << 1];
void dfs(int u, int f) {
    dfn[u] = ++ stp, fa[u] = f; dep[u] = dep[f] + 1;
    for (auto v : g[u]) if (v != f) dfs(v, u);
}

void init() {
    rep(i,2,stp) lgv[i] = lgv[i >> 1] + 1;
    rep(i,1,stp) st[0][dfn[i]] = fa[i];
    rep(i,1,lgv[stp]) for (int j = 1, u, v; j + (1 << i) - 1 <= stp; ++ j) 
        u = st[i-1][j], v = st[i-1][j + (1 << i - 1)], st[i][j] = dep[u] < dep[v] ? u : v;
}

int qry(int u, int v) {
    if (u == v) return u;
    u = dfn[u], v = dfn[v];
    if (u > v) swap(u, v); u ++;
    int d = lgv[v - u + 1];
    u = st[d][u], v = st[d][v - (1 << d) + 1];
    return dep[u] < dep[v] ? u : v;
}

void init2() {
    rep(i,1,n-1) st[0][i] = f[i];
    rep(i,1,lgv[n-1]) for (int j = 1; j + (1 << i) <= n; ++ j) 
        st[i][j] = max(st[i-1][j], st[i-1][j + (1 << i - 1)]);
}

int query(int l, int r) {
    if (l > r) return 0;
    int d = lgv[r - l + 1];
    return max(st[d][l], st[d][r - (1 << d) + 1]);
}

signed main() {
    get(T);
    while (T--) {
        get(n, m, q); cnt = n;
        rep(i,1,(n<<1) - 1) g[i].clear();
        init(n);
        rep(i,1,m) {
            get(l, r);
            l = find(l), r = find(r);
            if (l == r) continue;
            ++ cnt;
            g[cnt].push_back(ele[l]), g[cnt].push_back(ele[r]);
            g[ele[l]].push_back(cnt), g[ele[r]].push_back(cnt);
            dsu[l] = r, a[cnt] = i, ele[r] = cnt;
        } 
        dfs(cnt, 0); init();
        rep(i,1,n-1) f[i] = a[qry(i, i + 1)];
        init2();
        rep(i,1,q) {
            get(l, r);
            cout << query(l, r - 1) << ' ';
        } cout << '\n';
    }
}


CF1446C

给定序列 \(a\),保证 \(a_i\) 两两不同。每个 \(i\)会向序列中满足 \(a_i\oplus a_j\)\(\oplus\) 代表异或)最小的 \(j\) 连双向边,并且如果 \(j\) 也向 \(i\) 连了边,只算一条边。现在要删去序列中的一些数,使得最后形成的图是一棵树,输出最少删除数。

\(1\le n\le 2\times 10^5, 1\le a_i \le 10^9\)

转化成最大保留数计算,答案转为 $n – $ 答案即可。

考虑异或最小才连边的性质,我们将所有数插入一棵 trie。容易发现只有最深层节点才会和他的兄弟连边,这就组成了一系列长度不超过 \(2\) 的环。
\(f_i\)\(i\) 节点子树内元素两两有边的最大保留数。容易发现当 \(i\) 的孩子数小于 \(2\) 时直接继承儿子即可。考虑两个儿子的情况。由于一边剩余超过两个节点的话一定会在内部连边,因此定有一边需要删除节点到只剩一个。取两边较小的删除即可。

在 trie 上进行一遍搜索即可得到答案。复杂度 \(O(n\log a)\)

code
#include <bits/stdc++.h>
using namespace std;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 10;
int n, a[N], mx, lgmx;
 
int trie[N << 5][2], mlc;
void insert(int u) {
    int ptr = 0, ztr;
    pre(i,lgmx,0) {
        ztr = u >> i & 1;
        if (trie[ptr][ztr] == 0) trie[ptr][ztr] = ++ mlc;
        ptr = trie[ptr][ztr];
    }
}
 
int dfs(int u) {
    if (!trie[u][0] and !trie[u][1]) return 1;
    if (!trie[u][0] and trie[u][1]) return dfs(trie[u][1]);
    if (trie[u][0] and !trie[u][1]) return dfs(trie[u][0]);
    return max(dfs(trie[u][0]), dfs(trie[u][1])) + 1;
}
 
signed main() {
    get(n); rep(i,1,n) get(a[i]), mx = max(mx, a[i]); lgmx = __lg(mx);
    rep(i,1,n) insert(a[i]);
    cout << n - dfs(0);
}


CF455D

给定长度为 \(n\) 的序列 \(a\),共有 \(q\) 个询问,支持两种操作:

  • 1 l r 将区间 \([l,r]\) 依次向右移动一位,其中 \(a_r\) 移动到 \(a_l\)

  • 2 l r k 询问区间 \([l,r]\)\(k\) 出现次数。

强制在线。

\(1\le n,q\le 10^5,1\le a_i\le n\)

这题我没打正解。

首先考虑静态情况怎么写。
这是简单的,我们开 \(n\) 个 vector,第 \(i\) 个 vector 维护值为 \(i\) 的元素的编号集合,每次查询时找到落在前缀区间内的元素个数后差分得到答案。

由于每次只会移动一个元素,考虑动态为元素重编号。
具体地,我们维护一个平衡树表示当前所有元素的编号。假设要插入的位置前面的元素的编号为 \(l\),位置后面的元素的编号为 \(r\),则为更改元素重编号为 \(\frac{l+r}2\)
询问时找到第 \(l,r\) 个编号,仍然在对应值的集合中寻找前缀后差分即可。

这引发了一个问题:若数据是特殊构造的,则连续向一个位置插入会导致两个点的编号以指数速度趋近,最后精度丢失成为相同点。
这显然无法再使用平衡树维护了。
怎么办呢?
答案是遇到困难睡大觉。
当产生这种情况时我们直接退出程序就能过这题的 hack 数据了。因为这题的 hack 完全没有一次询问。

当然这也是可以解决的。可能的方案有采取不同的重编号策略多次重复尝试或 \(O(\frac{\sqrt{n}} {\log {n}})\) 次插入后暴力重构等。
不想写。
正解是平衡树维护这个相对顺序,或者分块。
不想写。

因为不想写 \(n+1\) 棵平衡树所以仍然是 vector 维护编号集合。
因为不想写 \(1\) 棵平衡树所以仍然是 pb_ds 的平衡树。
所以码很短。

code
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e5 + 10;
int n, q, typ, l, r, x, ans;
vector <double> bv[N];
__gnu_pbds :: tree <pair<double, int>, __gnu_pbds :: null_type, less<pair<double,int> >, 
            __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update> T;

signed main() {
    get(n); rep(i,1,n) get(x), bv[x].push_back(i * 1e18), T.insert( { i * 1e18, x } );
    get(q); while ( q-- ) {
        get(typ, l, r); l = (l + ans - 1) % n + 1; r = (r + ans - 1) % n + 1; if (l > r) swap(l, r);
        if (typ == 1) {
            auto lft = prev(T.find_by_order(l - 1)), rgt = T.find_by_order(l - 1), rpos = T.find_by_order(r - 1);
            bv[rpos->second].erase(lower_bound(bv[rpos->second].begin(), bv[rpos->second].end(), rpos->first));
            bv[rpos->second].insert(upper_bound(bv[rpos->second].begin(), bv[rpos->second].end(), (lft->first + rgt->first) / 2), (lft->first + rgt->first) / 2);
            int tmp = rpos->second;
            T.erase(rpos);
            if ((T.find( {(lft->first + rgt->first) / 2, tmp} ) != T.end())) return 0;
            T.insert( {(lft->first + rgt->first) / 2, tmp} );
        } else {
            auto lft = T.find_by_order(l - 1), rgt = T.find_by_order(r - 1);
            get(x); x = (x + ans - 1) % n + 1;
            cout << ( ans = (upper_bound(bv[x].begin(), bv[x].end(), rgt->first) - bv[x].begin()) -
                            (lower_bound(bv[x].begin(), bv[x].end(), lft->first) - bv[x].begin()) ) << '\n';
        }
    }
}


CF1635F

给定 \(n\) 个二元组 \((x_i,w_i)\),按照 \(x_i\) 严格递增排序给出。给出 \(q\) 次询问,每次询问给出 \(l,r\),你需要输出:

\[\min_{l\le i<j\le r} |x_i-x_j| \times (w_i+w_j) \]

\(2\le n,q \le 3\times 10^5, |x_i|\le 10^9, 1\le w_i \le 10^9\)

性质题。

我们从左右两边对 \(w\) 做单调栈,栈内维护小于 \(w_i\) 的值中 \(x\) 最接近 \(x_i\) 的元素。设 \(l_i\) 为从左边做到 \(i\) 位置时 \(i\) 入栈前的栈顶,\(r_i\) 为从右边做时的栈顶。我们需要证明只有 \((i,l_i)\)\((i,r_i)\) 可能对答案造成贡献。
证明:
假设 \((i,j)\) 是更优的答案且 \(j\neq l_i, j\neq r_i\),则我们知道 \(w_{l_i} \le w_j, |x_{l_i} – x_i| < |x_j – x_i|\)。因此 \(j\) 的答案一定不更优。
\(r_i\) 的情况同理。

于是我们就只需要维护 \(O(n)\) 对元素对答案的贡献了。容易发现每对元素是一个带权区间,需要询问区间完全包含才能贡献权值,最终答案是权值最小值。
这个直接树状数组倒着扫一遍就行了。在区间结束时以区间开始位置作为下标插入区间权值即可。

答案最大 \(4\times 10^{18}\),记得 \(\inf\) 设大点。

code
#include <bits/stdc++.h>
using namespace std;
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 3e5 + 10; typedef long long ll;
int n, q, x[N], w[N], t1, t2;
vector <tuple<int, int, ll> > vec[N];
int stk[N], top;
ll ans[N];

#define calc(i, j) (1ll * abs(x[i] - x[j]) * (w[i] + w[j]))

struct BIT {
    ll Index[N]; 
    void add(int p, ll v) { for (; p <= n; p += p & -p) Index[p] = min(Index[p], v); }
    ll qry(int p) { ll ret = 4e18 + 10; for (; p; p ^= p & -p) ret = min(ret, Index[p]); return ret; }
} B;

signed main() {
    get(n, q); rep(i,1,n) get(x[i], w[i]), B.Index[i] = 4e18 + 10;
    stk[++top] = 0;
    rep(i,1,n) {
        while (stk[top] and w[stk[top]] > w[i]) -- top;
        if (stk[top]) vec[stk[top]].emplace_back(0, i, calc(stk[top], i));
        stk[++top] = i;
    }
    stk[top = 1] = 0;
    pre(i,n,1) {
        while (stk[top] and w[stk[top]] > w[i]) -- top;
        if (stk[top]) vec[i].emplace_back(0, stk[top], calc(stk[top], i));
        stk[++top] = i;
    }
    rep(i,1,q) get(t1, t2), vec[t1].emplace_back(i, t2, 0);
    
    pre(i,n,1) {
        for (auto [id, pos, val] : vec[i]) 
            if (id == 0) {
                B.add(pos, val);
            } else {
                ans[id] = B.qry(pos);
            }
    } rep(i,1,q) cout << ans[i] << '\n';
}

原文地址:http://www.cnblogs.com/joke3579/p/chitchat221103.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性