线段树合并

江天一色无纤尘,皎皎空中孤月轮。江畔何人初见月,江月何年初照人。
人生代代无穷已,江月年年望相似。不知江月待何人,但见长江送流水。

做了多少忘了多少,翻开提交记录全是抄的题解,连变量名是啥意思都想不起来了。

A. Promotion Counting

“高二上几调的solution”中让我去启发式合并,于是就想到了复习一下。

记录个数,如果不用线段树合并的话,就需要从每个节点往下找它的孩子,每换一个点都需要清空一次,线段树合并的作用就是让已经找到的子节点的个数被利用。由于满足条件的标准在变化,不能用比较直接的方法来统计个数,比如dfs子树的大小。

对每一个数值,都单独的开一棵权值线段树。点的关系决定了这些线段树被合并到一起的方式。

and权值线段树:桶我们经常使用,例如基数排序是用cnt[]数组记录每个数出现的次数。权值线段树就是用线段树维护一个桶,它可以O(log2v)(v是值域)查询某个范围内的数出现的总次数,需要按值域开空间,当值域过大时需要离散化或动态开点。每个叶子节点的值代表这个值的出现次数,非叶子结点的值代表它管辖的值域内所有值出现的次数的和。

复习一下发现这个题还挺板子的……

离散化或动态开点用一个就够了,下面的code选择了离散化。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 10;
const int Inf = 0xfffffff;

#define re register
#define lson(x) tree[x].lson
#define rson(x) tree[x].rson

int n, c[maxn], vl[maxn], cnt, fa[maxn], rt[maxn], ans[maxn], tot;
vector<int> q[maxn];

struct node
{
    int lson, rson, dt, tg;
}tree[maxn*40];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

//dt是个数,tg是位置,也就是加的是谁的权值
inline void pushup(int rt)
{
    tree[rt].dt = tree[lson(rt)].dt + tree[rson(rt)].dt;
}

inline int update(int rt, int l, int r, int pos)
{
    if(!rt) rt = ++tot;
    if(l == r)
    {
        tree[rt].dt++; tree[rt].tg = pos;
        //printf("tree[%d].dt == %d  tree[%d].tg == %d\n", rt, tree[rt].dt, rt, tree[rt].tg);
        return rt;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) lson(rt) = update(lson(rt), l, mid, pos);
    else rson(rt) = update(rson(rt), mid+1, r, pos);
    pushup(rt);
    return rt;
}

inline int segmerge(int ra, int rb, int l, int r)
{
    if(!ra) return rb;
    if(!rb) return ra;
    if(l == r)
    {
        tree[ra].dt += tree[rb].dt;
        //printf("%d %d\n", tree[ra].tg, tree[rb].tg);
        tree[ra].tg = tree[rb].tg;
    }
    int mid = (l + r) >> 1;
    lson(ra) = segmerge(lson(ra), lson(rb), l, mid);
    rson(ra) = segmerge(rson(ra), rson(rb), mid+1, r);
    //注意第二行是rson(ra) 不能写成rson(rb)
    pushup(ra);
    return ra;
}

inline int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return tree[rt].dt;
    }
    int mid = (l + r) >> 1;
    int val = 0;
    if(L <= mid) val += query(tree[rt].lson, l, mid, L, R);
    if(R > mid) val += query(tree[rt].rson, mid+1, r, L, R);
    return val;
}

void dfs(int x)
{
    int sz = q[x].size();
    for(int i=0; i<sz; i++)
    {
        int to = q[x][i];
        if(to == fa[x]) continue;
        dfs(to);
        rt[x] = segmerge(rt[x], rt[to], 1, cnt);//把子树合并到根
        //printf("rt[%d] == %d\n", x, rt[x]);
    }
    //printf("--rt[%d] == %d\n", x, rt[x]);
    ans[x] = query(rt[x], 1, cnt, vl[x]+1, cnt);
    //printf("ans[%d] == %d\n", x, ans[x]);
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        vl[i] = read(); c[i] = vl[i];
    }
    sort(c+1, c+1+n);
    cnt = unique(c+1, c+1+n) - c - 1;
    for(int i=1; i<=n; i++)
    {
        vl[i] = lower_bound(c+1, c+1+n, vl[i]) - c;
        rt[i] = update(rt[i], 1, cnt, vl[i]);//每一个数值建一棵权值树
    }
    for(int i=2; i<=n; i++)
    {
        int x = read();
        fa[i] = x;
        q[x].push_back(i);
        q[i].push_back(x);
    }
    dfs(1);
    for(int i=1; i<=n; i++)
    {
        printf("%d\n", ans[i]);
    }

    return 0;
}
View Code

B. bzoj4399: 魔法少女LJJ

唯一的印象是,当初这道题比较难调,抄题解很容易串行

1.新建一个节点,权值为x。 2.连接两个节点。 3.将一个节点a所属于的联通快内权值小于x的所有节点权值变成x。 4.将一个节点a所属于的联通快内权值大于x的所有节点权值变成x。 5.询问一个节点a所属于的联通块内的第k小的权值是多少。 6.询问一个节点a所属联通快内所有节点权值之积与另一个节点b所属联通快内所有节点权值之积的大小。 7.询问a所在联通快内节点的数量

哦,第二个印象也回忆起来了:这玩意儿和线段树合并有什么关系?

第三个印象:什么鬼?这也能离线?疯了吧……不过后来也确实不是我想象中的那种疯狂离线,只是记录了一下值域,先把每个可能作为点权的数值放到数组里,问题的顺序当然不能变。

取log是因为6要求比较权值之积的大小,log是单调函数,不影响相对值,还可以把乘法转化成加法,既避免了超数据范围,又使积的记录有了传递性。至于连通块,就用并查集来维护,线段树合并就是在这里用到了,两个合并同步进行,注意根和孩子要一一对应,尽管两个单独来看方向不重要,但合起来就不一样了,线段树类型还是权值线段树,用并查集如果有断边就麻烦了,好在数据范围是op<=7,后面的操作不用管它。懒惰标记和清空函数是一套操作,这是把区间修改拆成了先删除再添加两个操作。查找第k小的权值的操作和平衡树的原理简直一样,我说当时学平衡树的那部分操作怎么有种似曾相识的感觉……

and离散化和动态开点也不是不能同时用。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 400005;
const int Inf = 0xfffffff;

#define re register

int m, n, d[maxn], tot, cnt;
double val[maxn*18];
int rt[maxn], fa[maxn];
bool mark[maxn*18];
int lc[maxn*18], rc[maxn*18], Size[maxn*18];

struct node2
{
    int op, x, y;
}q[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int find(int x)
{
    if(fa[x] == x) return x;
    return fa[x] = find(fa[x]);
}

int idx(int x)
{
    return lower_bound(d+1, d+n+1, x) - d;
}

void pushup(int rt)
{
    val[rt] = val[lc[rt]] + val[rc[rt]];
    Size[rt] = Size[lc[rt]] + Size[rc[rt]];
}

void pushnow(int rt)
{
    Size[rt] = val[rt] = 0;
    mark[rt] = 1;
}

void pushdown(int rt)
{
    if(!mark[rt]) return;
    pushnow(lc[rt]), pushnow(rc[rt]);
    mark[rt] = 0;
}

void insert(int &rt, int l, int r, int pos, int num1, double num2)
{
    int mid = (l + r) >> 1;
    if(!rt) rt = ++tot;
    pushdown(rt);
    if(l == r)
    {
        Size[rt] = num1, val[rt] = num2;
        return;
    }
    if(pos <= mid) insert(lc[rt], l, mid, pos, num1, num2);
    else insert(rc[rt], mid+1, r, pos, num1, num2);
    pushup(rt);
}

int Merge2(int x, int y, int l, int r)
{
    if(!x || !y) return x+y;
    val[x] += val[y], Size[x] += Size[y];
    int mid = (l + r) >> 1;
    if(l == r) return x;
    pushdown(x), pushdown(y);
    lc[x] = Merge2(lc[x], lc[y], l, mid);
    rc[x] = Merge2(rc[x], rc[y], mid+1, r);
    return x;
}

void Merge1(int x, int y)
{
    x = find(x), y = find(y);
    if(x != y) fa[y] = x, rt[x] = Merge2(rt[x], rt[y], 1, n);
}

int query(int rt, int l, int r, int x, int y)
{
    if(!rt) return 0;
    if(x <= l && r <= y) return Size[rt];
    int mid = (l + r) >> 1;
    pushdown(rt);
    if(y <= mid) return query(lc[rt], l, mid, x, y);
    if(x > mid) return query(rc[rt], mid+1, r, x, y);
    return query(lc[rt], l, mid, x, y) + query(rc[rt], mid+1, r, x, y);
}

void modify(int rt, int l, int r, int L, int R)
{
    if(!rt) return;
    if(L <= l && r <= R)
    {
        return pushnow(rt);
    }
    int mid = (l + r) >> 1;
    pushdown(rt);
    if(L <= mid) modify(lc[rt], l, mid, L, R);
    if(R > mid) modify(rc[rt], mid+1, r, L, R);
    pushup(rt);
}

int ask(int rt, int l, int r, int x)
{
    if(l == r) return d[l];
    int mid = (l + r) >> 1; pushdown(rt);
    if(Size[lc[rt]] >= x) return ask(lc[rt], l, mid, x);
    return ask(rc[rt], mid+1, r, x-Size[lc[rt]]);
}

int main()
{
    int x, y;
    m = read();
    for(int i=1; i<=m; i++)
    {
        q[i].op = read(); q[i].x = read();
        if(q[i].op != 1 && q[i].op != 7) q[i].y = read();
        if(q[i].op == 3 || q[i].op == 4) d[++n] = q[i].y;
        if(q[i].op == 1) d[++n] = q[i].x;
    }
    sort(d+1, d+1+n);
    n = unique(d+1, d+1+n) - (d+1);
    for(int i=1; i<=m; i++)
    {
        if(q[i].op == 1)
        {
            insert(rt[++cnt], 1, n, idx(q[i].x), 1, log2(q[i].x));
            fa[cnt] = cnt;
        }
        else if(q[i].op == 2) Merge1(q[i].x, q[i].y);
        else if(q[i].op == 3)
        {
            x = find(q[i].x), y = idx(q[i].y);
            int sum = query(rt[x], 1, n, 1, y);
            modify(rt[x], 1, n, 1, y);
            insert(rt[x], 1, n, y, sum, sum*log2(q[i].y));
        }
        else if(q[i].op == 4)
        {
            x = find(q[i].x), y = idx(q[i].y);
            int sum = query(rt[x], 1, n, y, n);
            modify(rt[x], 1, n, y, n);
            insert(rt[x], 1, n, y, sum, sum*log2(q[i].y));
        }
        else if(q[i].op == 5) printf("%d\n", ask(rt[find(q[i].x)], 1, n, q[i].y));
        else if(q[i].op == 6) printf("%d\n", (val[rt[find(q[i].x)]] > val[rt[find(q[i].y)]]) ? 1 : 0);
        else printf("%d\n", Size[rt[find(q[i].x)]]);
    }

    return 0;
}
View Code

D. 雨天的尾巴

说起树上差分,我忽然想到了一个差分的题,和线段树没什么关系,打算把它放在这里收藏一下,我看到那个题时的第一印象好像使用分块做……奇奇妙妙。

>>插入:同是D题好巧合……

D. 小 X 与煎饼达人

区间的正反改变可以用差分数组,求和就成了当前位置的状态。脑洞说我又想到了树状数组的区间修改单点查询,啊我又联想到了一个题——

——啊不过还是先把这个题的code放在这:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e6 + 2;
const int Inf = 0xfffffff;

#define re register
int n, m;
int c[maxn], ans;
ll sum[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    freopen("flip.in", "r", stdin);
    freopen("flip.out", "w", stdout);

    n = read(); m = read();
    for(int i=1; i<=m; i++)
    {
        int x = read(), y = read();
        c[x]++; c[y+1]--;
    }
    for(int i=1; i<=n; i++)
    {
        sum[i] = sum[i-1];
        sum[i] += c[i];
        if(sum[i] & 1) ans++;
    }
    printf("%d", ans);

    return 0;
}
View Code

B. 冒泡排序(2022高考集训2)

代码里的注释好像够了,那我就不再写一遍了。

/*
专业抄题解100年 你看到我自嘲地苦笑了吗
*/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 3;
const int mod = 1e9 + 7;
const int INF = 0x7fffffff;

int n, tree1[5005], tree2[5005], num[5005], st1[5005], st2[5005];
int dp[5005][5005];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void up(int a, int b, int *s)
{
    while(a <= n)
    {
        s[a] += b;
        a += a & (-a);
    }
    return;
}

inline int down(int &a, const int &s)
{
    if(s == 1)
    {
        return tree1[a] + st1[a-(a&(-a))];
    }
    else
    {
        return tree2[a] + st2[a-(a&(-a))];
    }
}
/*
我就不信我看不出来这到底是什么意思!!
*/

int main()
{
    freopen("mp.in", "r", stdin);
    freopen("mp.out", "w", stdout);

    int ret = 0;
    n = read();
    for(int i=1; i<=n; i++)
    {
        num[i] = read() + 1;
        if(num[i] == i)
        {
            printf("0");
            exit(0);
        }
        else if(num[i] > i)
        {
            up(i, 1, tree1);//在第一个树状数组上单点修改
            //本来应该是区间修改,树状数组表示优先级,而优先级一定是连续的
            up(num[i]-1, -1, tree1);//大数往后换
            //printf("1: up(%d, 1)\n", i);
            //printf("1: up(%d, -1)\n", num[i]-1);
        }
        else
        {
            up(num[i], 1, tree2);
            up(i-1, -1, tree2);//小数往前换
            //printf("2: up(%d, 1)\n", num[i]);
            //printf("2: up(%d, -1)\n", i-1);
        }
    }
    for(int i=1; i<n; i++)
    {
        st1[i] = down(i, 1);
        st2[i] = down(i, 2);
        //printf("st1[%d] == %d\n", i, st1[i]);
        //printf("st2[%d] == %d\n", i, st2[i]);
        //为什么需要分别对两棵树查询前缀和
        //为什么要算这么多前缀和
        //好吧我才知道这是差分数组。。
        //所以st1就表示当前点是不是对于后面的点优先(先和后面换在和前面换)
        //st2表示是不是对于前面的点优先
        if(st1[i] && st2[i])//那么优先级出现了矛盾
        {
            printf("0");
            exit(0);
        }
    }
    st1[0] = st2[0] = st1[n] = st2[n] = 0;
    dp[1][1] = 1;
    //f[a][b]第a个点在第b个位置被交换
    for(int i=2,j=1; i<n; j=i,i++)//int j=i-1
    {
        if(st1[j])
        {
            for(int k=2; k<=i; k++)//新的点如果排在k,那么i一定要在j之前的任意位置被交换
            {
                dp[i][k] = dp[i][k-1] + dp[j][k-1];
                if(dp[i][k] >= mod)
                {
                    dp[i][k] -= mod;
                }
            }
        }
        else if(st2[j])
        {
            for(int k=j; k; k--)
            {
                dp[i][k] = dp[i][k+1] + dp[j][k];
                if(dp[i][k] >= mod)
                {
                    dp[i][k] -= mod;
                }
            }
        }
        else//随便的关系
        {
            for(int k=1; k<=j; k++)
            {
                dp[i][1] += dp[j][k];
                if(dp[i][1] >= mod)
                {
                    dp[i][1] -= mod;
                }
            }
            for(int k=2; k<=i; k++)
            {
                dp[i][k] = dp[i][k-1];
            }
        }
    }
    for(int i=1; i<n; i++)
    {
        ret += dp[n-1][i];
        if(ret >= mod)
        {
            ret -= mod;
        }
    }
    printf("%d", ret);

    return 0;
}
View Code

跑偏的思路还是要跑回来:不过,休息一下,未完待续?算了,直接到蓝书382页附近去找吧,懒了。

posted @ 2022-07-15 14:03  Catherine_leah  阅读(49)  评论(1编辑  收藏  举报
/* */