数据结构专题2

数据结构专题2 - step 1

线段树

1. Cube - HDU 3584

三维的空间中有 n n n 个元素,初始时每个空间元素均为0。更新操作是0变1,1变0,是一个立方体型区域内的所有元素都更新。然后查询是问某个点的元素是0还是1. ( 1 < = n < = 100 , m < = 10000 1<=n<=100, m <=10000 1<=n<=100,m<=10000)

三维树状数组,维护三维差分数组,所有加法减法都是异或,就可以了。

对于 n n n 维差分或者是 n n n 维前缀和,公式一定有 2 n 2^n 2n 项,根据 x 2 , y 2 , z 2 . . . x_2, y_2,z_2... x2,y2,z2... 这些东西出现次数的奇偶性就可以判断出来符号了.

#include
using namespace std;
const int N = 110;
int tr[N][N][N];
int n, m;
int lowbit(int x)
{
    return x & -x;
}
void add(int x, int y, int z)
{
    for(int i = x; i <= n; i += lowbit(i))
    {
        for(int j = y; j <= n; j += lowbit(j))
        {
            for(int k = z; k <= n; k += lowbit(k))
            {
                tr[i][j][k] ^= 1;
            }
        }
    }
}
int sum(int x, int y, int z)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i))
    {
        for(int j = y; j; j -= lowbit(j))
        {
            for(int k = z; k; k -= lowbit(k))
            {
                res ^= tr[i][j][k];
            }
        }
    }
    return res;
}
void modify(int x1, int y1, int z1, int x2, int y2, int z2)
{
    add(x1, y1, z1);
    add(x1, y1, z2 + 1);
    add(x1, y2 + 1, z1);
    add(x1, y2 + 1, z2 + 1);
    add(x2 + 1, y1, z1);
    add(x2 + 1, y1, z2 + 1);
    add(x2 + 1, y2 + 1, z1);
    add(x2 + 1, y2 + 1, z2 + 1);
}
int main()
{
    while(cin >> n >> m)
    {
        memset(tr, 0, sizeof tr);
        int op, x1, y1, z1, x2, y2, z2;
        for(int i = 1; i <= m; i++)
        {
            scanf("%d", &op);
            if(!op)
            {
                scanf("%d%d%d", &x1, &y1, &z1);
                printf("%d\n", sum(x1, y1, z1));
            }
            else
            {
                scanf("%d%d%d%d%d%d", &x1, &y1, &z1, &x2, &y2, &z2);
                modify(x1, y1, z1, x2, y2, z2);
            }
        }
    }
    return 0;
}

2. Assignment

长度为 n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n105) 的序列,找到有多少个连续区间,满足区间内的最大值减最小值小于 k k k

这种需要枚举所有连续子序列的问题,基本都是暴力枚举一个端点,然后看另一个端点有多少个是满足的. 固定一个端点之后,区间的极差一定是单调的,因此二分就可以了.

其实现在看来只需要双指针就可以了

注意这个题是多组输入,因此求 s t st st 表的时候,要小心不要超过 n n n 的边界.

#include
using namespace std;
const int N = 100010, M = 18;
int lg[N], a[N], n, k;
int Fmax[N][M], Fmin[N][M];
typedef long long ll;
void pre()
{
    for(int j = 0; j < M; j++)
    {
        for(int i = 1; i <= n; i++)
        {
            if(!j) Fmin[i][j] = Fmax[i][j] = a[i];
            else if(i + (1 << (j - 1)) <= n)
            {
                Fmax[i][j] = max(Fmax[i][j - 1], Fmax[i + (1 << (j - 1))][j - 1]);
                Fmin[i][j] = min(Fmin[i][j - 1], Fmin[i + (1 << (j - 1))][j - 1]);
            }
            else
            {
                Fmax[i][j] = Fmax[i][j - 1], Fmin[i][j] = Fmin[i][j - 1];
            }
        }
    }
}
int query(int l, int r)
{
    int len = lg[r - l + 1];
    return max(Fmax[l][len], Fmax[r - (1 << len) + 1][len]) - min(Fmin[l][len], Fmin[r - (1 << len) + 1][len]);
}
int main()
{
    for(int i = 1; i < N; i++) lg[i] = log2(i);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

        pre();

        ll ans = 0;
        for(int i = 1; i <= n; i++)
        {
            int l = 1, r = i;
            while(r > l)
            {
                int mid = (l + r) / 2;
                if(query(mid, i) >= k) l = mid + 1;
                else r = mid;
            }
            ans += (i - l + 1);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

3. GCD

给你 n n n 个数, Q Q Q 次询问,问你 ( l , r ) (l,r) (l,r) 区间的 g c d gcd gcd 是多少,并且有多少子串的 g c d gcd gcd 和询问的 g c d gcd gcd 是相同的。

$n \le 10^5,Q \le 10^5, a_i \le 10^9 $

任意区间的 g c d gcd gcd 可以用 s t st st 表快速求得。然后我们预处理出每一个 g c d gcd gcd 有多少个。方法是这样的:暴力枚举右端点,那么前面的 g c d gcd gcd 一定是一段一段的. 那么我们可以把前面一段一段的 gcd 存下来,每次加进来一个数,前面会有若干区间的 gcd 下降. 把gcd相同的区间合并成一个区间.

小心答案爆 int.

#include
using namespace std;
const int N = 100010, M = 18;
typedef long long ll;
typedef pair<int, int> P;
#define x first
#define y second

int a[N], f[N][M], lg[N];

unordered_map<int, ll> mp;
int n, m, kase;
void pre()
{
    mp.clear();
    for(int j = 0; j < M; j++)
    {
        for(int i = 1; (i + (1 << j) - 1) <= n; i++)
        {
            if(!j) f[i][j] = a[i];
            else f[i][j] = __gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
        }
    }
    vector<P> vec;
    for(int i = 1; i <= n; i++)
    {
        for(auto& p : vec)
        {
            p.x = __gcd(p.x, a[i]);
        }
        vec.push_back({a[i], 1});

        vector<P> tmp;
        for(auto p : vec)
        {
            if(tmp.size() && tmp.back().x == p.x) tmp.back().y += p.y;
            else tmp.push_back(p);
            mp[p.x] += p.y;
        }
        vec = tmp;
    }
}

int query(int l, int r)
{
    int len = lg[r - l + 1];
    return __gcd(f[l][len], f[r - (1 << len) + 1][len]);
}

int main()
{
    for(int i = 1; i < N; i++) lg[i] = log2(i);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d\n", &n);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
        }
        pre();
        printf("Case #%d:\n", ++kase);
        scanf("%d", &m);
        for(int i = 1; i <= m; i++)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            int d = query(l, r);
            printf("%d %lld\n", d, mp[d]);
        }
    }
    return 0;
}

4. City Driving (值得再写)

n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n105) 个点 n n n 条无向边的连通图, Q ( Q ≤ 1 0 4 ) Q(Q \le 10^4) Q(Q104) 个询问,问两点之间的最短路是多少。

基环树,任意删掉一条边,假设是 ( a , b ) (a, b) (a,b). 若询问 u , v u, v u,v 的最小值,则答案在 d i s t ( u , v ) , d i s t ( u , a ) + w ( a , b ) + d i s t ( b , v ) dist(u, v), dist(u, a) + w(a, b) + dist(b, v) dist(u,v),dist(u,a)+w(a,b)+dist(b,v) d i s t ( u , b ) + w ( a , b ) + d i s t ( a , v ) dist(u, b) + w(a, b) + dist(a, v) dist(u,b)+w(a,b)+dist(a,v)​ 三者取最小值.

无向图基环树,一定要记录from。不然找自环的时候,找的就是无向边的自环.(自行分析)

最近公共祖先还得再写几次,一写一堆bug

#include
using namespace std;
const int N = 100010, M = N * 2;
int h[N], e[M], ne[M], w[M], idx;

inline void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

int n, m;
bool st[N], ins[N];
int rmid, rma, rmb;

void dfs_c(int u, int from)
{
    st[u] = ins[u] = true;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        //无向图基环树,一定要记录from。不然找自环的时候,找的就是无向边的自环.
        if(i == (from ^ 1)) continue;
        int v = e[i];
        if(!st[v]) dfs_c(v, i);
        else if(ins[v])
        {
            rmid = i, rma = u, rmb = v;
        }
    }
    ins[u] = false;
}

//小心记录两个depth,一个用作lca的查询,一个用作查询到根节点的距离。因为这个树的边是有权重的.
int fa[N][18], dep[N], ndep[N];

void bfs()
{
    queue<int> que;
    for(int i = 1; i <= n; i++) ndep[i] = dep[i] = -1;
    
    dep[1] = 1, ndep[1] = 0;
    
    que.push(1);
    
    for(int k = 0; k < 18; k++) fa[1][k] = 1;
    
    while(que.size())
    {
        int u = que.front(); que.pop();
        for(int i = h[u]; i != -1; i = ne[i])
        {
            if(i == rmid || (i ^ 1) == rmid) continue;
            int v = e[i];
            if(dep[v] == -1)
            {
                dep[v] = dep[u] + 1;
                ndep[v] = ndep[u] + w[i];
                fa[v][0] = u;
                for(int k = 1; k < 18; k++) fa[v][k] = fa[fa[v][k - 1]][k - 1];
                que.push(v);
            }
        }
    }
}

int lca(int a, int b)
{
    if(dep[a] > dep[b]) swap(a, b);
    
    for(int k = 17; k >= 0; k--)
    {
        if(dep[a] <= dep[fa[b][k]]) b = fa[b][k];
    }
    if(a == b) return a;

    for(int k = 17; k >= 0; k--)
    {
        if(fa[a][k] != fa[b][k]) a = fa[a][k], b = fa[b][k];
    }
    return fa[a][0];
}

int dist(int u, int v)
{
    int anc = lca(u, v);
    //printf("** u = %d  dep[u] = %d,  v = %d  dep[v] = %d,  anc = %d\n", u, dep[u], v, dep[v], anc);
    return ndep[u] + ndep[v] - 2 * ndep[anc];
}

int main()
{
    while(cin >> n, n)
    {
        for(int i = 1; i <= n; i++)
        {
            h[i] = -1, st[i] = false;
        }
        idx = 0;
        for(int i = 1; i <= n; i++)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            a++, b++;
            add(a, b, c), add(b, a, c);
        }
        dfs_c(1, -1);
        bfs();
        
        //printf("** remove id = %d, rma = %d, rmb = %d\n", rmid, rma, rmb);
        scanf("%d", &m);
        for(int i = 1; i <= m; i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            u++, v++;
            int ans = dist(u, v);
            ans = min(dist(u, rma) + w[rmid] + dist(rmb, v), ans);
            ans = min(dist(u, rmb) + w[rmid] + dist(rma, v), ans);
            printf("%d\n", ans);
        }
    }
    return 0;
}

5. Rikka with Phi (值得再写)

给出一个长度为 n n n​ 的数组 A A A​,接下来有 m m m​ 次操作

1 l r:对所有区间 [ l , r ] [l,r] [l,r]​ 中的整数 i i i​,把 A [ i ] A[i] A[i]​ 变成 φ ( A [ i ] ) φ(A[i]) φ(A[i])​ (指欧拉函数)

2 l r x:对所有区间 [ l , r ] [l,r] [l,r]​ 中的整数 i i i​,把 A [ i ] A[i] A[i]​ 变成 x x x

3 l r:询问 [ l , r ] [l,r] [l,r]​ 的区间和

n , m ( n ≤ 3 × 1 0 5 , m ≤ 3 × 1 0 5 ) n,m(n≤3×10^5,m≤3×10^5) n,m(n3×105,m3×105) 1 ≤ A [ i ] ≤ 1 0 7 1≤A[i]≤10^7 1A[i]107

保证任何时刻 1 ≤ A i ≤ 1 0 7 1 \le A_i \le 10^7 1Ai107

几个减少代码量的技巧:

  1. 由于数组元素任意时刻都保证是正的,因此可以记录一个 x x x​. 如果 x = 0 x = 0 x=0​,则说明当前区间所有元素并非全部相等。若 x > 0 x > 0 x>0,则区间所有元素都等于 x x x.
  2. 第一个修改操作,若当前区间元素都相等,则可转化为第二个修改操作.
#include
#define lson u << 1
#define rson u << 1 | 1

typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;
typedef __int128_t i128;
typedef __uint128_t u128;

using namespace std;
const int N = 300010;


const int M = 10000010;
int prime[M], cnt, phi[M];
bool st[M];
void sieve(int n)
{
    phi[1] = 1;
    for(int i = 2; i <= n; i++)
    {
        if(!st[i]) prime[cnt++] = i, phi[i] = i - 1;
        for(int j = 0; prime[j] * i <= n; j++)
        {
            st[i * prime[j]] = true;
            if(i % prime[j] == 0)
            {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            }
            phi[i * prime[j]] = phi[i] * (prime[j] - 1);
        }
    }
}

struct node
{
    int l, r, x;
    bool lazy;
    u64 sum;
}tr[N * 4];
int a[N];

void pushup(int u)
{
    tr[u].sum = tr[lson].sum + tr[rson].sum;
    if(tr[lson].x && tr[lson].x == tr[rson].x)
    {
        tr[u].x = tr[lson].x;
    }
    else tr[u].x = 0;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        tr[u].x = a[l];
        tr[u].sum = a[l];
    }
    else
    {
        int mid = l + r >> 1;
        build(lson, l, mid), build(rson, mid + 1, r);
        pushup(u);
    }
}

void pushdown(int u)
{
    if(tr[u].lazy)
    {
        tr[lson].lazy = tr[rson].lazy = true;
        tr[lson].x = tr[rson].x = tr[u].x;
        tr[lson].sum = 1ll * tr[lson].x * (tr[lson].r - tr[lson].l + 1);
        tr[rson].sum = 1ll * tr[rson].x * (tr[rson].r - tr[rson].l + 1);
    }
    tr[u].lazy = false;
}

void modify1(int u, int x, int L, int R)
{
    if(L <= tr[u].l && tr[u].r <= R)
    {
        tr[u].lazy = true;
        tr[u].x = x;
        tr[u].sum = 1ll * x * (tr[u].r - tr[u].l + 1);
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(L <= mid) modify1(lson, x, L, R);
        if(R > mid) modify1(rson, x, L, R);
        pushup(u);
    }
}

void modify2(int u, int L, int R)
{
    if(L <= tr[u].l && tr[u].r <= R)
    {
        if(tr[u].x)
        {
            modify1(u, phi[tr[u].x], L, R);
        }
        else
        {
            pushdown(u);
            int mid = tr[u].l + tr[u].r >> 1;
            modify2(lson, L, R), modify2(rson, L, R);
            pushup(u);
        }
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(L <= mid) modify2(lson, L, R);
        if(R > mid) modify2(rson, L, R);
        pushup(u);
    }
}

u64 query(int u, int L, int R)
{
    if(L <= tr[u].l && tr[u].r <= R)
    {
        return tr[u].sum;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    u64 res = 0;
    if(L <= mid) res += query(lson, L, R);
    if(R > mid) res += query(rson, L, R);
    return res;
}

int main()
{
    sieve(M - 1);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        int n, m;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        build(1, 1, n);

        for(int i = 1; i <= m; i++)
        {
            int op, l, r, x;
            scanf("%d%d%d", &op, &l, &r);
            if(op == 1)
            {
                modify2(1, l, r);
            }
            else if(op == 2)
            {
                scanf("%d", &x);
                modify1(1, x, l, r);
            }
            else
            {
                printf("%llu\n", query(1, l, r));
            }
        }
    }
    return 0;
}

6. P4145 上帝造题的七分钟 2

第一行一个整数 n n n,代表数列中数的个数。

第二行 n n n 个正整数,表示初始状态下数列中的数。

第三行一个整数 m m m,表示有 m m m 次操作。

接下来 m m m 行每行三个整数 k l r

  • k = 0 k=0 k=0 表示给 [l,r] 中的每个数开平方(下取整)。
  • k = 1 k=1 k=1 表示询问 [l,r] 中各个数的和。

数据中有可能 l > r l>r l>r,所以遇到这种情况请交换 l l l r r r

#include

#define lson u << 1
#define rson u << 1 | 1

typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;
typedef __int128_t i128;
typedef __uint128_t u128;

const u32 mod = 1e9 + 7;

using namespace std;
const int N = 100010;

struct node
{
    int l, r;
    u64 sum, x;
}tr[N * 4];

u64 a[N];

void pushup(int u)
{
    tr[u].sum = tr[lson].sum + tr[rson].sum;
    if(tr[lson].x && tr[lson].x == tr[rson].x)
    {
        tr[u].x = tr[lson].x;
    }
    else tr[u].x = 0;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        tr[u].sum = tr[u].x = a[l];
    }
    else
    {
        int mid = l + r >> 1;
        build(lson, l, mid), build(rson, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int L, int R)
{
    if(tr[u].l == tr[u].r) tr[u].x = tr[u].sum = sqrt(tr[u].x);
    else if(L <= tr[u].l && tr[u].r <= R)
    {
        if(tr[u].x == 1)
        {
            return;
        }
        else
        {
            int mid = tr[u].l + tr[u].r >> 1;
            if(L <= mid) modify(lson, L, R);
            if(R > mid) modify(rson, L, R);
            pushup(u);
        }
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(L <= mid) modify(lson, L, R);
        if(R > mid) modify(rson, L, R);
        pushup(u);
    }
}

u64 query(int u, int L, int R)
{
    if(L <= tr[u].l && tr[u].r <= R)
    {
        return tr[u].sum;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    u64 res = 0;
    if(L <= mid) res += query(lson, L, R);
    if(R > mid) res += query(rson, L, R);
    return res;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%llu", &a[i]);
    }
    build(1, 1, n);
    
    int m;
    scanf("%d", &m);
    
    for(int i = 1; i <= m; i++)
    {
        int k, l, r;
        scanf("%d%d%d", &k, &l, &r);
        if(l > r) swap(l, r);

        if(k == 0)
        {
            modify(1, l, r);
        }
        else
        {
            printf("%llu\n", query(1, l, r));
        }
    }
    return 0;
}

7. 247. 亚特兰蒂斯 - AcWing题库

用懒标记维护的版本。minlen 表示当前区间涵盖所有区间, c n t cnt cnt​ 最小的时候区间长度之和.

这样子,维护的其实是覆盖 0 0 0 次的区间长度之和. 最后总长度减去它就可以了.

#include
#define x first
#define y second
#define lowbit(x) ((x) & (-x))
#define all(x) x.begin(), x.end()
#define pcount  __builtin_popcount
#define lcount  __builtin_clz
#define rcount  __builtin_ctz
#define qcin std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
#define tempt template<class T>
#define lson u << 1
#define rson u << 1 | 1

typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;

using namespace std;
const int N = 10010;

struct node
{
    int l, r, mincnt;
    int lazy;
    double minlen, len;
}tr[N * 8];

struct seg
{
    double x, y1, y2;
    int t;
    bool operator < (const seg& s) const
    {
        return x < s.x;
    }
}segs[N * 2];

vector<double> ys;

int find(double x)
{
    return lower_bound(ys.begin(), ys.end(), x) - ys.begin();
}

void pushup(int u)
{
    tr[u].mincnt = min(tr[lson].mincnt, tr[rson].mincnt);
    double minlen = 0;
    if(tr[u].mincnt == tr[lson].mincnt) minlen += tr[lson].minlen;
    if(tr[u].mincnt == tr[rson].mincnt) minlen += tr[rson].minlen;
    tr[u].minlen = minlen;
}

void pushdown(int u)
{
    tr[lson].mincnt += tr[u].lazy, tr[rson].mincnt += tr[u].lazy;
    tr[lson].lazy += tr[u].lazy, tr[rson].lazy += tr[u].lazy;

    tr[u].lazy = 0;
}

void build(int u, int l, int r)
{
    double len = ys[r + 1] - ys[l];
    tr[u] = {l, r, 0, 0, len, len};
    if(l == r) return;
    else
    {
        int mid = l + r >> 1;
        build(lson, l, mid), build(rson, mid + 1, r);
    }
}

void modify(int u, int L, int R, int t)
{
    if(L <= tr[u].l && tr[u].r <= R)
    {
        tr[u].mincnt += t;
        tr[u].lazy += t;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(L <= mid) modify(lson, L, R, t);
        if(R > mid) modify(rson, L, R, t);
        pushup(u);
    }
}

int kase;
int main()
{
    int n;
    while(cin >> n, n)
    {
        int sz = 0;
        ys.clear();

        for(int i = 1; i <= n; i++)
        {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            segs[sz++] = {x1, y1, y2, 1};
            segs[sz++] = {x2, y1, y2, -1};
            ys.push_back(y1), ys.push_back(y2);
        }
        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());

        sort(segs, segs + 2 * n);

        build(1, 0, ys.size() - 2);

        double ans = 0;

        for(int i = 0; i < 2 * n; i++)
        {
            if(i)
            {
                double len = tr[1].len;
                if(!tr[1].mincnt) len -= tr[1].minlen;
                
                ans += len * (segs[i].x - segs[i - 1].x);
            }
            modify(1, find(segs[i].y1), find(segs[i].y2) - 1, segs[i].t);
            //printf("%f %d %f\n", tr[1].len, tr[1].mincnt, tr[1].minlen);
        }
        printf("Test case #%d\nTotal explored area: %.2f\n\n", ++kase, ans);
    }

    return 0;
}

8. Legacy (值得再写)

三种操作:
1 a b c,在建立权值为 c c c​ 的 a → b a\rightarrow b ab​ 的单向边
2 a b c d,建立 a → [ b , c ] a\rightarrow [b,c] a[b,c]​ 权值为 d d d​ 的单向边
3 a b c d,建立 [ b , c ] → a [b,c]\rightarrow a [b,c]a​ 权值为 d d d​​ 的单向边

给你一个起点,问你起点到其他点的最短路长度

1   ≤   n ,   q   ≤   1 0 5 1 ≤ n, q ≤ 10^5 1 n,q 105

借助线段树建立为每个区间建立一个结点,有一个需要注意的问题,为了保证某个点可以走到它对应的所有区间,并且它所有对应的区间能走到它,并且不能形成长度为 0 0 0 的环。因此可以建立两棵线段树,一棵从父结点向子结点连长度为 0 0 0 的边,一棵从子结点向父结点连长度为 0 0 0 的边. 子结点之间相互连长度为 0 0 0 的边.

当然我们不用真的建立两棵线段树,只需要把对应的线段树编号找到就可以了. 第一个线段树的编号加 4 n 4n 4n 就是第二棵线段树的编号.

#include
#define x first
#define y second
typedef long long ll;

using namespace std;
const int N = 100010, M = 6000010;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

struct node
{
    int l, r;
}tr[N * 4];

int h[N * 8], e[M], ne[M], id[N], idx;
ll w[M], d[N * 8];

int n, q, s;
typedef pair<ll, int> P;
bool st[N * 8];

void add(int a, int b, ll c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        id[l] = u;
        add(u, u + 4 * n, 0), add(u + 4 * n, u, 0);
    }
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        add(u, u << 1, 0), add(u, u << 1 | 1, 0);
        add((u << 1) + 4 * n, u + 4 * n, 0), add((u << 1 | 1) + 4 * n, u + 4 * n, 0);
    }
}

void modify(int u, int l, int r, int x, ll c, int op)
{
    if(l <= tr[u].l && tr[u].r <= r)
    {
        if(op == 2) add(x, u, c), add(x + 4 * n, u + 4 * n, c);
        else add(u, x, c), add(u + 4 * n, x + 4 * n, c);
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, x, c, op);
        if(r > mid) modify(u << 1 | 1, l, r, x, c, op);
    }
}

void dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[id[s]] = 0;
    priority_queue<P, vector<P>, greater<P>> que;
    que.push({0, id[s]});
    while(que.size())
    {
        int u = que.top().y; que.pop();
        if(st[u]) continue;
        st[u] = true;
        for(int i = h[u]; i != -1; i = ne[i])
        {
            int v = e[i];
            if(d[v] > d[u] + w[i])
            {
                d[v] = d[u] + w[i];
                que.push({d[v], v});
            }
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d%d", &n, &q, &s);
    build(1, 1, n);
    for(int i = 1; i <= q; i++)
    {
        int op, a, b, c, d;
        scanf("%d%d%d%d", &op, &a, &b, &c);
        if(op == 1)
        {
            //注意是 id[a] 而不是 a
            add(id[a], id[b], c);
            add(id[a] + 4 * n, id[b] + 4 * n, c);
        }
        else if(op == 2)
        {
            scanf("%d", &d);
            //注意是 id[a] 而不是 a
            modify(1, b, c, id[a], d, 2);
        }
        else
        {
            scanf("%d", &d);
            //注意是 id[a] 而不是 a
            modify(1, b, c, id[a], d, 3);
        }
    }

    dijkstra();
    for(int i = 1; i <= n; i++)
    {
        //注意是 d[id[i]] 不是 d[i]
        printf("%lld%c", d[id[i]] == INF ? -1LL : d[id[i]], i == n ? '\n' : ' ');
    }
    return 0;
}

9. Ch’s gift (值得再写)

题意:给定一棵树。询问 s , t s,t s,t 之间路径上的点权和,点权在 [ a , b ] [a,b] [a,b]​ 内才有效,否则该点权为 0 0 0

从每个节点向父结点建立可持久化权值线段树,查询的时候就是 r o o t [ s ] + r o o t [ t ] − r o o t [ l c a ( s , t ) ] − r o o t [ f a [ l c a ( s , t ) ] ] root[s] +root[t]-root[lca(s,t)]-root[fa[lca(s,t)]] root[s]+root[t]root[lca(s,t)]root[fa[lca(s,t)]].

终于调出来了

第一个错误:主席树由于维护的是权值,查询的是点权之和。因此从子结点向父结点建立版本的时候,要 tr[p].sum += nums[x],不是 tr[p].sum = nums[x]

第二个错误:注意找 [ a , b ] [a,b] [a,b] 区间的时候,一个是找 ≥ a \ge a a 的最小值,一个是找 ≤ b \le b b 的最大值,不能全用 lower_bound 处理.

#include
using namespace std;
const int N = 100010, M = N * 2;
typedef long long ll;
int h[N], e[M], ne[M], idx;
ll w[N];

int fa[N][18];
int n, m;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int q[N], dep[N];
void bfs()
{
    memset(dep, -1, sizeof dep);
    dep[0] = 0, dep[1] = 1;
    int hh = 0, tt = -1;
    q[++tt] = 1;
    while(hh <= tt)
    {
        int u = q[hh++];
        for(int i = h[u]; i != -1; i = ne[i])
        {
            int v = e[i];
            if(dep[v] != -1) continue;
            dep[v] = dep[u] + 1;
            fa[v][0] = u;
            for(int j = 1; j < 18; j++) fa[v][j] = fa[fa[v][j - 1]][j - 1];
            q[++tt] = v;
        }
    }
}

int lca(int a, int b)
{
    if(dep[a] < dep[b]) swap(a, b);
    for(int k = 17; k >= 0; k--)
    {
        if(dep[fa[a][k]] >= dep[b])
        {
            a = fa[a][k];
        }
    }
    if(a == b) return a;
    for(int k = 17; k >= 0; k--)
    {
        if(fa[a][k] != fa[b][k])
        {
            a = fa[a][k], b = fa[b][k];
        }
    }
    return fa[a][0];
}

struct node
{
    int l, r;
    ll sum;
}tr[N * 4 + N * 17];

int cnt;
vector<ll> nums;
int root[N];
int find(ll x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
void pushup(int u)
{
    tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}

int build(int l, int r)
{
    int p = ++cnt;
    if(l == r)
    {
        tr[p] = {l, r, 0};
        return p;
    }
    int mid = l + r >> 1;
    tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
    pushup(p);
    return p;
}

int insert(int p, int l, int r, int x)
{
    int q = ++cnt;
    tr[q] = tr[p];
    if(l == r)
    {
        tr[q].sum += nums[x];
        return q;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
    else tr[q].r = insert(tr[p].r, mid + 1, r, x);
    pushup(q);
    return q;
}

ll query(int s, int t, int anc, int anc_fa, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return tr[s].sum + tr[t].sum - tr[anc].sum - tr[anc_fa].sum;
    int mid = l + r >> 1;
    ll res = 0;
    if(L <= mid) res += query(tr[s].l, tr[t].l, tr[anc].l, tr[anc_fa].l, l, mid, L, R);
    if(R > mid) res += query(tr[s].r, tr[t].r, tr[anc].r, tr[anc_fa].r, mid + 1, r, L, R);
    
    return res;
}

void dfs(int u, int fa)
{
    root[u] = insert(root[fa], 0, nums.size() - 1, find(w[u]));
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if(v == fa) continue;
        dfs(v, u);
    }
}

int main()
{
    while(cin >> n >> m)
    {
        memset(h, -1, sizeof h);
        idx = 0, cnt = 0;
        nums.clear();
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld", &w[i]);
            nums.push_back(w[i]);
        }
        sort(nums.begin(), nums.end());
        nums.erase(unique(nums.begin(), nums.end()), nums.end());

        for(int i = 1; i < n; i++)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }

        bfs();
        root[0] = build(0, nums.size() - 1);
        dfs(1, 0);

        for(int i = 1; i <= m; i++)
        {
            int s, t;
            ll a, b;
            scanf("%d%d%lld%lld", &s, &t, &a, &b);
            int anc = lca(s, t);

            int anc_fa = fa[anc][0];
            a = find(a), b = upper_bound(nums.begin(), nums.end(), b) - nums.begin() - 1;

            ll ans = query(root[s], root[t], root[anc], root[anc_fa], 0, nums.size() - 1, a, b);
            printf("%lld%c", ans, i == m ? '\n' : ' ');
        }
    }
    return 0;
}

11. ROT-Tree Rotations (值得再写)

题意:给定一颗有 n n n叶节点的二叉树。每个叶节点都有一个权值 p i p_i pi(注意,根不是叶节点),所有叶节点的权值构成了一个 1 ∼ n 1 \sim n 1n 的排列。对于这棵二叉树的任何一个结点,保证其要么是叶节点,要么左右两个孩子都存在。现在你可以任选一些节点,交换这些节点的左右子树。在最终的树上,按照先序遍历遍历整棵树并依次写下遇到的叶结点的权值构成一个长度为 n n n 的排列,你需要最小化这个排列的逆序对数。输出这个逆序对数。

线段树合并裸题,每个叶节点建立权值线段树,看看交换与不交换对答案的贡献分别是多少

我们要求的是逆序对最小,对于一个节点,逆序对有三种:

  • 在左子树中。
  • 在右子树中。
  • 跨越了左右子树。

我们清楚,交换子树只会对第三种情况产生影响。因此,我们只需要在合并线段树的过程中统计交换子树的逆序对个数 u u u 和不交换子树的逆序对个数 v v v,取 m i n ( u , v ) min(u,v) min(u,v) 累加到答案中就行了。

现在的问题是如何找第三种情况下的逆序对。

很明显,对于除了叶节点的每一个节点:

  1. 如果不交换: u + = [ p . r s ] . s i z e ∗ [ q . l s ] . s i z e u+=[p.rs].size*[q.ls].size u+=[p.rs].size[q.ls].size
  2. 如果交换: v + = [ p . l s ] . s i z e ∗ [ q . r s ] . s i z e v+=[p.ls].size*[q.rs].size v+=[p.ls].size[q.rs].size

重点:每一次合并线段树时,递归到除了叶节点的所有节点,都要累加逆序对个数 u,v

#include
using namespace std;
const int N = 100010;
typedef long long ll;
struct node
{
    int l, r, sz;
}tr[N * 50];
int n, idx;
ll res1, res2, ans;

int modify(int l, int r, int x)
{
    int q = ++idx;
    tr[q].sz = 1;
    if(l == r) return q;
    int mid = l + r >> 1;
    if(x <= mid) tr[q].l = modify(l, mid, x);
    else tr[q].r = modify(mid + 1, r, x);
    return q;
}

int merge(int p, int q, int l, int r)
{
    if(!p || !q) return p ? p : q;
    if(l == r)
    {
        tr[p].sz += tr[q].sz;
        return p;
    }
    else
    {
        res1 += 1ll * tr[tr[p].r].sz * tr[tr[q].l].sz;
        res2 += 1ll * tr[tr[p].l].sz * tr[tr[q].r].sz;
        int mid = l + r >> 1;
        tr[p].l = merge(tr[p].l, tr[q].l, l, mid);
        tr[p].r = merge(tr[p].r, tr[q].r, mid + 1, r);
        tr[p].sz = tr[tr[p].l].sz + tr[tr[p].r].sz;
        return p;
    }
}

int dfs()
{
    int x;
    scanf("%d", &x);
    if(!x)
    {
        int l = dfs(), r = dfs();
        res1 = res2 = 0;
        int p = merge(l, r, 1, n);
        ans += min(res1, res2);
        return p;
    }
    else
    {
        int p = modify(1, n, x);
        return p;
    }
}

int main()
{
    scanf("%d", &n);
    dfs();
    printf("%lld\n", ans);
}

11.

给出 A [ ] A[] A[], 要求支持:
1 询问 A [ l . . r ] A[l..r] A[l..r] 中最⼤的数
2 删除 A [ x ] A[x] A[x], 并且 x + 1 x+1 x+1 以后的元素整体前移
3 在末尾增加⼀个数

用 splay 肯定是可以的。不过线段树更简单

每个位置记录一个 0 / 1 0/1 0/1. 0 0 0 表示数字被删掉, 1 1 1 表示数字没被删。记录当前 [ 1... r ] [1...r] [1...r] 未被删掉数的数量是 s u m [ r ] sum[r] sum[r]. 那么用线段树二分,在 [ 1 , n ] [1,n] [1,n] 上面找第 s u m [ r ] sum[r] sum[r] 小的数字.

12.

对长度为 n n n 的数列进行 m m m 次操作, 操作为:
1 a [ l . . r ] a[l..r] a[l..r] 每⼀项都加⼀个常数 C C C, 其中 0 ≤ C ≤ 1 0 11 0 ≤ C ≤ 10^{11} 0C1011
2 求 F [ a [ l ] ] + F [ a [ l + 1 ] ] + . . . F [ a [ r ] ] m o d    10000 F[a[l]]+F[a[l+1]]+...F[a[r]] \mod 10000 F[a[l]]+F[a[l+1]]+...F[a[r]]mod10000 的余数
其中 F [ i ] F[i] F[i] 表示斐波那契数列.
F [ 0 ] = F [ 1 ] = 1 F[0]=F[1]=1 F[0]=F[1]=1, F [ n + 2 ] = F [ n + 1 ] + F [ n ] F[n+2]=F[n+1]+F[n] F[n+2]=F[n+1]+F[n]

每一项保存 F [ a [ i ] ] F[a[i]] F[a[i]] F [ a [ i + 1 ] ] F[a[i + 1]] F[a[i+1]] 即可,然后用 lazy tag 记下来幂次.

然后操作的时候就是
( F [ a i ] F [ a i + 1 ] ) ( 0 1 1 1 ) \begin{pmatrix}F[a_i] & F[a_i + 1]\end{pmatrix} \begin{pmatrix}0 & 1 \\ 1 & 1\end{pmatrix} (F[ai]F[ai+1])(0111)

13. Hotel - POJ 3667 (值得再写)

  • 给一个长度为 n ( n ≤ 5 ∗ 1 0 4 ) n(n \le 5 * 10^4) n(n5104) 的序列,一开始都是 0 0 0,有 q q q 种操作:找到一段长度为 k k k 的全为 0 0 0 的子串,使开始位置尽可能得小,输出起始位置,然后把这段全部变成1,找不到的话输出 0 0 0;使得一段区间全部变成 0 0 0.

每个点维护左起一段连续最长的空住房,以及右起一段连续的最长的空住房,在表示的区间内最长的连续空住房数。

如何合并信息?

前两个很好维护,但是要特判一边全部都是空住房的时候。

最后一个也好维护,首先用两边原来的答案来更新。把两个区间拼接起来会导致中间的连续一段空住房变长,所以还需要用它来更新答案。

对于lazy标记的下放,因为是覆盖,所以直接改就好了。

这道题的WA了很久才过 懒标记之间会有相互限制,就比如这个题,lazy0 和 lazy1 一定不会同时为1,因此一旦修改其中一个是1,另一个必须要修改为0.

当然这道题的懒标记开一个就行了,因为他们是相互限制的嘛.

#include
#include
#include
using namespace std;
const int N = 50010;
struct node
{
    int l, r;
    int lmax, rmax, mmax;
    int lazy;
}tr[N * 4];
int n, m;
void pushup(int u)
{
    if(tr[u << 1].mmax == tr[u << 1].r - tr[u << 1].l + 1)
    {
        tr[u].lmax = tr[u << 1].mmax + tr[u << 1 | 1].lmax;
    }
    else tr[u].lmax = tr[u << 1].lmax;
    if(tr[u << 1 | 1].mmax == tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)
    {
        tr[u].rmax = tr[u << 1].rmax + tr[u << 1 | 1].mmax;
    }
    else tr[u].rmax = tr[u << 1 | 1].rmax;
    tr[u].mmax = max(max(tr[u << 1].mmax, tr[u << 1 | 1].mmax), tr[u << 1].rmax + tr[u << 1 | 1].lmax);
}

void pushdown(int u)
{
    if(tr[u].lazy != -1)
    {
        tr[u << 1].lmax = tr[u << 1].rmax = tr[u << 1].mmax = (tr[u << 1].r - tr[u << 1].l + 1) * (tr[u].lazy ^ 1);
        tr[u << 1 | 1].lmax = tr[u << 1 | 1].rmax = tr[u << 1 | 1].mmax = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * (tr[u].lazy ^ 1);
        tr[u << 1].lazy = tr[u << 1 | 1].lazy = tr[u].lazy;
    }
    tr[u].lazy = -1;
}

void build(int u, int l, int r)
{
    tr[u].l = l,  tr[u].r = r;
    if(l == r)
    {
        tr[u].lmax = tr[u].rmax = tr[u].mmax = 1;
        tr[u].lazy = -1;
    }
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
void modify(int u, int l, int r, int x)
{
    if(l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].lazy = x;
        tr[u].lmax = tr[u].rmax = tr[u].mmax = (tr[u].r - tr[u].l + 1) * (x ^ 1);
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, x);
        if(r > mid) modify(u << 1 | 1, l, r, x);
        pushup(u);
    }
}
int query(int u, int k)
{
    if(tr[u].mmax < k) return 0;
    if(tr[u].l == tr[u].r) return tr[u].l;

    pushdown(u);
    
    if(tr[u << 1].mmax >= k) return query(u << 1, k);
    if(tr[u << 1].rmax + tr[u << 1 | 1].lmax >= k) return tr[u << 1].r - tr[u << 1].rmax + 1;
    else return query(u << 1 | 1, k);
}

int main()
{
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i = 1; i <= m; i++)
    {
        int op, l, k;
        scanf("%d", &op);
        if(op == 1)
        {
            scanf("%d", &k);
            int res = query(1, k);
            printf("%d\n", res);
            if(res) modify(1, res, res + k - 1, 1);
        }
        else
        {
            scanf("%d%d", &l, &k);
            modify(1, l, l + k - 1, 0);
        }
    }
    return 0;
}

14. 可持久化线段树 1(可持久化数组)

如题,你需要维护这样的一个长度为 NN 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值
  2. 访问某个历史版本上的某一位置的值

此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)

简单的可持久化线段树,不需要建立权值线段树

注意数组千万别开小,因为询问是 1 0 6 10^6 106 次.

#include
using namespace std;
const int N = 1000010;
struct node
{
    int l, r, value;
}tr[N * 4 + N * 20];
//小心询问次数达到1e6,数组千万别开小.
int n, m, a[N], root[N], idx;

int build(int l, int r)
{
    int q = ++idx;
    if(l == r)
    {
        tr[q].value = a[l];
        return q;
    }
    int mid = l + r >> 1;
    tr[q].l = build(l, mid), tr[q].r = build(mid + 1, r);
    return q;
}

int modify(int p, int l, int r, int x, int v)
{
    int q = ++idx;
    tr[q] = tr[p];
    if(l == r)
    {
        tr[q].value = v;
        return q;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[q].l = modify(tr[p].l, l, mid, x, v);
    else tr[q].r = modify(tr[p].r, mid + 1, r, x, v);
    return q;
}

int query(int p, int l, int r, int x, int& res)
{
    int q = ++idx;
    tr[q] = tr[p];
    if(l == r)
    {
        res = tr[q].value;
        return q;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[q].l = query(tr[p].l, l, mid, x, res);
    else tr[q].r = query(tr[p].r, mid + 1, r, x, res);
    return q;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    root[0] = build(1, n);
    for(int i = 1; i <= m; i++)
    {
        int v, op, loc, value;
        scanf("%d%d%d", &v, &op, &loc);
        if(op == 1)
        {
            scanf("%d", &value);
            root[i] = modify(root[v], 1, n, loc, value);
        }
        else
        {
            int res;
            root[i] = query(root[v], 1, n, loc, res);
            printf("%d\n", res);
        }
    }
    return 0;
}

15. 可持久化并查集

给定 n 个集合,第 i 个集合内初始状态下只有一个数,为 i。

有 m 次操作。操作分为 3 种:

  • 1 a b 合并 a,b 所在集合;
  • 2 k 回到第 k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态;
  • 3 a b 询问 a,b 是否属于同一集合,如果是则输出 1 ,否则输出 0。

按秩合并的并查集,只要 f a fa fa 数组确定, d e p dep dep 状态确定,这个并查集的就唯一确定。

有几个需要注意的地方:

  1. 一定小心可持久化线段树的版本结点个数是 m ∗ log ⁡ n m * \log n mlogn 个,版本个数是 m + 1 m+1 m+1 个, r o o t root root 的数组要开到 M M M.
  2. 版本信息啥的要弄好,一定看清楚当前的 r o o t [ i ] root[i] root[i] 的信息有没有更新
  3. 按秩合并有启发式合并的感觉,就是把高度低的树的树根连到高度高的树根上面去,这样子可以保证复杂度; r k rk rk 维护的是树的高度。需要注意的是(不妨假设 r k [ a ] ≤ r k [ b ] rk[a] \le rk[b] rk[a]rk[b])当 r k [ a ] < r k [ b ] rk[a] < rk[b] rk[a]<rk[b] 的时候, a a a 连到 b b b 不会使 b b b 的高度发生变化,但是如果 r k [ a ] = r k [ b ] rk[a] = rk[b] rk[a]=rk[b] r k [ b ] rk[b] rk[b] 的高度就会发生变化. 这个时候就要 r k [ b ] + + rk[b]++ rk[b]++. 千万不要加错了,不然复杂度会炸.
#include
using namespace std;
const int N = 100010, M = 200010;

struct node
{
    int l, r, fa, rk;
}tr[N * 4 + 20 * M];
//想清楚内存,是 M * log N.

int n, m, idx, root[M];

int build(int l, int r)
{
    int q = ++idx;
    if(l == r)
    {
        tr[q].fa = l;
        return q;
    }
    int mid = l + r >> 1;
    tr[q].l = build(l, mid), tr[q].r = build(mid + 1, r);
    return q;
}

int modify(int p, int l, int r, int x, int v)
{
    int q = ++idx;
    tr[q] = tr[p];
    if(l == r)
    {
        tr[q].fa = v;
        return q;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[q].l = modify(tr[p].l, l, mid, x, v);
    else tr[q].r = modify(tr[p].r, mid + 1, r, x, v);
    return q;
}

void query(int q, int l, int r, int x, int& fa, int& rk)
{
    if(l == r)
    {
        fa = tr[q].fa, rk = tr[q].rk;
        return;
    }
    int mid = l + r >> 1;
    if(x <= mid) query(tr[q].l, l, mid, x, fa, rk);
    else query(tr[q].r, mid + 1, r, x, fa, rk);
}

int find(int q, int x, int& rk)
{
    int fa;
    query(q, 1, n, x, fa, rk);
    if(fa == x) return x;
    return find(q, fa, rk);
}

void add(int q, int l, int r, int x)
{
    if(l == r)
    {
        tr[q].rk++;
        return;
    }
    int mid = l + r >> 1;
    if(x <= mid) add(tr[q].l, l, mid, x);
    else add(tr[q].r, mid + 1, r, x);
}

int main()
{
    scanf("%d%d", &n, &m);
    root[0] = build(1, n);
    for(int i = 1; i <= m; i++)
    {
        int op, a, b, k;
        scanf("%d", &op);
        if(op != 2)
        {
            scanf("%d%d", &a, &b);
            int pa, da, pb, db;
            pa = find(root[i - 1], a, da), pb = find(root[i - 1], b, db);
            //这个版本信息的更新千万别忘,放在最前面!
            root[i] = root[i - 1];
            if(op == 1)
            {
                if(pa == pb) continue;
                //高度小的树连在高度大的树下方.
                if(da > db) swap(a, b), swap(da, db), swap(pa, pb);
                root[i] = modify(root[i - 1], 1, n, pa, pb);
                if(da == db) add(root[i], 1, n, pb);
            }
            else
            {
                printf("%d\n", int(pa == pb));
            }
        }
        else
        {
            scanf("%d", &k);
            root[i] = root[k];
        }
    }
}

平衡树

11. 无旋Treap

P3369 【模板】普通平衡树

无旋treap

#include
using namespace std;
const int N = 100010, INF = 0x3f3f3f3f;
mt19937 rnd(0);
struct node
{
    int key, l, r, sz, rnk;
}tr[N];
int tot, root;
void pushup(int u)
{
    tr[u].sz = tr[tr[u].l].sz + tr[tr[u].r].sz + 1;
}
void split(int rt, int& a, int& b, int k)
{
    if(rt == 0)
    {
        a = b = 0;
        return;
    }
    if(tr[rt].key <= k)
    {
        a = rt;
        split(tr[a].r, tr[a].r, b, k);
    }
    else
    {
        b = rt;
        split(tr[b].l, a, tr[b].l, k);
    }
    pushup(rt);
}
void merge(int&rt, int a, int b)
{
    if(a == 0 || b == 0)
    {
        rt = a + b;
        return;
    }
    if(tr[a].rnk < tr[b].rnk)
    {
        rt = b;
        merge(tr[b].l, a, tr[b].l);
    }
    else
    {
        rt = a;
        merge(tr[a].r, tr[a].r, b);
    }
    pushup(rt);
}
int add_node(int val)
{
    tr[++tot] = {val, 0, 0, 1, rnd()};
    return tot;
}
void insert(int& rt, int val)
{
    int x = 0, y = 0, nnode = add_node(val);
    split(rt, x, y, val);
    merge(x, x, nnode);
    merge(rt, x, y);
}
void remove(int& rt, int val)
{
    int x = 0, y = 0, z = 0;
    split(rt, x, y, val);
    split(x, x, z, val - 1);
    merge(z, tr[z].l, tr[z].r);
    merge(x, x, z);
    merge(rt, x, y);
}
int get_kth(int rt, int k)
{
    while(tr[tr[rt].l].sz + 1 != k)
    {
        if(tr[tr[rt].l].sz >= k) rt = tr[rt].l;
        else
        {
            k -= tr[tr[rt].l].sz + 1;
            rt = tr[rt].r;
        }
    }
    return tr[rt].key;
}
int get_rank(int& rt, int val)
{
    int x = 0, y = 0;
    split(rt, x, y, val - 1);
    int res = tr[x].sz + 1;
    merge(rt, x, y);
    return res;
}
int get_pre(int&rt, int val)
{
    int x = 0, y = 0;
    split(rt, x, y, val - 1);
    int res = get_kth(x, tr[x].sz);
    merge(rt, x, y);
    return res;
}
int get_scc(int& rt, int val)
{
    int x = 0, y = 0;
    split(rt, x, y, val);
    int res = get_kth(y, 1);
    merge(rt, x, y);
    return res;
}
void build()
{
    add_node(INF);
    root = 1;
    tr[root].sz = 0;
}
int main()
{
    int n;
    scanf("%d", &n);
    build();
    for(int i = 1; i <= n; i++)
    {
        int op, x;
        scanf("%d%d", &op, &x);

        if(op == 1)
        {
            insert(root, x);
        }
        else if(op == 2)
        {
            remove(root, x);
        }
        else if(op == 3)
        {
            printf("%d\n", get_rank(root, x));
        }
        else if(op == 4)
        {
            printf("%d\n", get_kth(root, x));
        }
        else if(op == 5)
        {
            printf("%d\n", get_pre(root, x));
        }
        else
        {
            printf("%d\n", get_scc(root, x));
        }
    }
    return 0;
}

P3391 【模板】文艺平衡树

无旋treap

Treap - OI Wiki (oi-wiki.org)

12. P3380 【模板】二逼平衡树(树套树) (补)

线段树套 无旋treap

树链剖分

13. Query on a tree

给一棵树,有两个操作,修改边权,查询 从 u u u v v v 的路径的边的最大值.

树链剖分裸题,把边权下沉至点权.

#include
#pragma GCC optimize(2)
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;

int h[N], e[M], ne[M], we[M], wv[N], idx;
int n;

inline void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], we[idx] = c, h[a] = idx++;
}

int sz[N], fa[N], dep[N], son[N];
void dfs1(int u, int father, int depth)
{
    sz[u] = 1, fa[u] = father, dep[u] = depth;
    son[u] = 0;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if(v == father) continue;
        wv[v] = we[i];
        dfs1(v, u, depth + 1);
        sz[u] += sz[v];
        if(sz[son[u]] < sz[v]) son[u] = v;
    }
}
int id[N], top[N], nw[N], cnt;

void dfs2(int u, int t)
{
    id[u] = ++cnt, nw[cnt] = wv[u], top[u] = t;
    if(!son[u]) return;
    dfs2(son[u], t);

    for(int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

struct node
{
    int l, r, mmax;
}tr[N * 4];

void pushup(int u)
{
    int t1 = tr[u << 1].mmax, t2 = tr[u << 1 | 1].mmax;
    tr[u].mmax = t1 > t2 ? t1 : t2;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)
    {
        tr[u].mmax = nw[l];
    }
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if(l <= tr[u].l && tr[u].r <= r) return tr[u].mmax;
    int mid = tr[u].l + tr[u].r >> 1;
    int res = 0;
    if(l <= mid)
    {
        int t = query(u << 1, l, r);
        if(t > res) res = t;
    }
    if(r > mid)
    {
        int t = query(u << 1 | 1, l, r);
        if(t > res) res = t;
    }
    return res;
}

void modify(int u, int x, int y)
{
    if(tr[u].l == x && tr[u].r == x) tr[u].mmax = y;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) modify(u << 1, x, y);
        else if(x > mid) modify(u << 1 | 1, x, y);
        pushup(u);
    }
}

int query_path(int a, int b)
{
    if(a == b) return 0;
    int res = 0;

    while(top[a] != top[b])
    {
        if(dep[top[a]] < dep[top[b]]) swap(a, b);
        int t = query(1, id[top[a]], id[a]);
        if(t > res) res = t;
        a = fa[top[a]];
    }
    if(dep[a] < dep[b]) swap(a, b);

    int t = query(1, id[b] + 1, id[a]);
    if(t > res) res = t;
    return res;
}

int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        memset(h, -1, sizeof h);
        idx = 0, cnt = 0;
        scanf("%d", &n);
        for(int i = 1; i < n; i++)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }
        dfs1(1, -1, 1);
        dfs2(1, 1);
        build(1, 1, n);
        char op[10];
        while(scanf("%s", op) && *op != 'D')
        {
            int a, b;
            scanf("%d%d", &a, &b);
            if(*op == 'Q') printf("%d\n", query_path(a, b));
            else
            {
                int edge = 2 * (a - 1);
                int u = e[edge], v = e[edge ^ 1];
                if(u == fa[v]) modify(1, id[v], b);
                else modify(1, id[u], b);
            }
        }
    }
    return 0;
}

15.Problem - 5405

题意:给出一棵节点上有权的树,两种操作:

1.修改一个点的权 w i w_i wi

2.询问 u , v u,v u,v,求 ∑ w i ∗ w j ∑w_i∗w_j wiwj, 其中 i到j的路径 和 u到v的路径 有公共点

难,不会

16. 染色

给定一棵有 n n n 个节点的无根树和 m m m 个操作,操作有 2 2 2 类:

1、将节点 a a a 到节点 b b b 路径上所有点都染成颜色 c c c

2、询问节点 a a a 到节点 b b b 路径上的颜色段数量(连续相同颜色被认为是同一段),如 “ 112221 112221 112221” 由 3 3 3 段组成:“ 11 11 11”、“ 222 222 222” 和 “ 1 1 1”。

请你写一个程序依次完成这 m m m 个操作。

链剖裸题,套个线段树。

这题唯一难的就在于查询颜色段的时候要考虑跨越查询区间时,要记得去掉重复的部分。合并区间的时候看一下两边区间相接的部分是否是相同颜色,相同的话减掉就可以了。

线段树维护这一段区间的左端点颜色,右端点颜色,区间不同颜色的和,记得 l a z y lazy lazy 标记。

动态树

18. OTOCI(补)

一个只有点的图

三种操作:link 操作, update 操作,query 链上的点权和

动态树(LCT

其他

5. Trains and Statistic

N N N​ 个车站 [ 1 , n ) [1,n) [1,n)​ 的车站可以买到编号为 i + 1 i+1 i+1​ 到 a i a_i ai​ 的车站的票,设 p i j p_{ij} pij​ 为 i i i​ 车站到 j j j​ 车站的最少买票数,求 ∑ i = 1 n ∑ j = i + 1 n p i j \sum\limits_{i=1}^{n}\sum\limits_{j=i + 1}^{n}p_{ij} i=1nj=i+1npij​​​

n ≤ 1 0 5 n \le 10^5 n105

d p i dp_i dpi 为从第 i i i 个车站出发,到达 [ i + 1 , n ] [i+1,n] [i+1,n] 车站需要的最小车票数量之和. 我们考虑 d p i dp_i dpi 从哪个 d p m dp_m dpm 转移过来,答案是在 m ∈ [ i + 1 , a i ] m \in [i + 1,a_i] m[i+1,ai] 找到 a m a_m am 最小的 m m m. 可以这么考虑:从 i i i 开始走两步可以到达的点,经过 m m m 可以到达最远的点,其他点能到的 m m m 也可以到. 因此拿 m m m 当中转节点是最优的.

9. BZOJ4990 Why Did the Cow Cross the Road II

上下有两个长度为n、位置对应的序列A、B,两个序列都是全排列

其中数的范围均为 1 ∼ n 1\sim n 1n​。若 a b s ( A [ i ] − B [ j ] ) < = 4 abs(A[i]-B[j]) <= 4 abs(A[i]B[j])<=4​,

A [ i ] A[i] A[i] B [ j ] B[j] B[j] 间可以连一条边。现要求在边与边不相交的情况下的最大的连边数量。

找到 A [ i ] A[i] A[i]​ 对应的 B B B​ 数组中的 9 9 9 个数字,然后把这 9 9 9 个数字按照降序排序,推进同一个数组里面,最后在此数组求最长上升子序列就是答案.

P1439 【模板】最长公共子序列

给出 1 , 2 , … , n 1,2,\ldots,n 1,2,,n 的两个排列 P 1 P_1 P1 P 2 P_2 P2 ,求它们的最长公共子序列。

对于 P 1 P_1 P1 的每一个数 i i i,记录 s e q [ i ] seq[i] seq[i] i i i P 2 P_2 P2 中的位置. 那么答案就是 s e q seq seq 的最长上升子序列。因为 P 1 P_1 P1 的下标上升的同时,要保证 P 2 P_2 P2 的下标也是上升的的.

2. Problem - 961E. Tufurama

#include
#define x first
#define y second

using namespace std;

typedef long long ll;
const int N = 200010;
ll ans;
int a[N], n, tr[N];
int lowbit(int x)
{
    return x & -x;
}
int sum(int x)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}
void add(int x, int c)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
typedef pair<int, int> P;

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    priority_queue<P, vector<P>, greater<P>> que;
    for(int i = 1; i <= n; i++)
    {
        while(que.size() && que.top().x < i)
        {
            add(que.top().y, -1);
            que.pop();
        }
        //printf("%d %d %d\n", i, a[i], sum(a[i]));
        ans += sum(min(a[i], n));
        que.push({a[i], i});
        add(i, 1);
    }
    printf("%lld\n", ans);
    return 0;
}

4. Problem - 980E - Codeforces

题意:给定一个 n n n 个结点的树,第 i i i 号点的权值是 2 i 2^i 2i,现在删除 k k k 个点,并且保证剩下的节点都是连通的,而且要保证权值之和最大。问需要删除哪些结点?

可以考虑贪心,由于我们只要能选择较大的结点就一定要选,因为此节点的权值大于比他编号小的所有节点权值之和。因为 n n n 号点是必须要选择的,因此以 n n n 号点为根,把 n n n 加入树 T ′ T' T 中。现在开始从大到小把结点加入树中,然后找到从 i i i T ′ T' T 的最短路径。

法一:采用树上倍增的方法快速解决(记录一下已经在 T ′ T' T 中的结点,然后倍增法计算出来它到最近的在 T ′ T' T 中的祖先的距离)。

法二:采用树状数组。怎样优化一个新节点 (记为 u u u ) 到离 T ′ T' T 中点 (记为 v v v) 的距离的最小值?即 u u u 到根节点的距离减去 u u u 到根节点的路径中已标明的结点的数量。已标明的结点数量怎么算呢?建立一个树的深搜序,相当于单点修改树的结点权值,把0改为1,然后查询某一结点到根节点的结点权值之和. 那么修改某个结点 u u u 权值,影响路径权值之和的结点是以 u u u 为子树的结点。那么在 [ i n [ u ] , o u t [ u ] ] [in[u], out[u]] [in[u],out[u]] 之间加1即可.

法二代码(万分小心树状数组那俩函数千万别写错!

#include
using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], idx;
int n, m, tr[N];
vector<int> ans;
bool st[N];
inline void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int lowbit(int x)
{
    return x & -x;
}
//别把两个函数搞反
void modify(int x, int c)
{
    //别写成 i += lowbit(x)
    for(int i = x; i <= n; i += lowbit(i))
    {
        //别写成 tr[i] += x
        tr[i] += c;
    }
}
int sum(int x)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i))
    {
        res += tr[i];
    }
    return res;
}
int cnt, in[N], out[N], fa[N], dep[N];

void dfs(int u, int father, int depth)
{
    in[u] = ++cnt, fa[u] = father, dep[u] = depth;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int v = e[i];
        if(v == father) continue;
        dfs(v, u, depth + 1);
    }
    out[u] = cnt;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    m = n - m;

    for(int i = 1; i < n; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    dfs(n, 0, 1);

    for(int i = n; i; i--)
    {
        if(dep[i] - sum(in[i]) <= m)
        {
            //printf("**  %d %d\n", dep[i], sum(in[i]));
            for(int u = i; u && !st[u]; u = fa[u])
            {
                st[u] = true;
                modify(in[u], 1), modify(out[u] + 1, -1);
                m--;
                //printf("%d %d %d\n", i, u, m);
            }
        }
    }

    for(int i = 1; i <= n; i++) if(!st[i]) ans.push_back(i);

    sort(ans.begin(), ans.end());
    for(int i = 0, sz = ans.size(); i < sz; i++)
    {
        printf("%d%c", ans[i], i + 1 == sz ? '\n' : ' ');
    }
    return 0;
}

你可能感兴趣的:(ACM题目整理,数据结构,算法)