暑假第五周学习笔记

周二

昨天打牛客多校了,所以没写博客,今天补题。

LCS(思维)

这题当时是我想的,做出来了。但是写的比较麻烦,我是先把abc排个序,然后再来构造,然后再反推回去。这个反推的过程挺烧脑的,很绕。不过最后还是AC了

看了题解,本质上是一样的,但是思路更清晰一些,也更好写一些

首先是一个转化,mi为a b c中最小的,那么先放mi个‘a’

这样就把三个LCS转化成了两个LCS,简单很多

接下来就无脑填就好了,比如a还有剩,就在第一个字符串和第二个字符串后加上‘b’

满足后,剩下就随便填填满就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

int main()
{
    int a, b, c, n;
    scanf("%d%d%d%d", &a, &b, &c, &n);

    int mi = min({a, b, c});
    a -= mi; b -= mi; c -= mi;
    if(a + b + c + mi > n)
    {
        puts("NO");
        return 0;
    }

    string s1(mi, 'a'), s2(mi, 'a'), s3(mi, 'a');
    while(a--)
    {
        s1.push_back('b');
        s2.push_back('b');
    }
    while(b--)
    {
        s2.push_back('c');
        s3.push_back('c');
    }
    while(c--)
    {
        s1.push_back('d');
        s3.push_back('d');
    }

    while(s1.size() < n) s1.push_back('x');
    while(s2.size() < n) s2.push_back('y');
    while(s3.size() < n) s3.push_back('z');
    cout << s1 << endl << s2 << endl << s3 << endl;

    return 0;
}

Average(二分答案)

这道题当时是队友用凸包加二分写的。看了题解还可以二分答案。都写一写

之后学学斜率优化再来补这题的斜率写法

当时是没有想到二分答案的。

如果设答案为k

那么就验证是否存在一个长度大于x的区间的平均值大于等于k

直接这样很难处理,因为这样等价于求某一段区间和大于len * k

而这个len >= x是不确定的,从而也就确定和S

这里用了一个技巧,就是每个数都减去k

从而就是平均均值大于等于0,从而就是和大于等于 len * 0 = 0

这样len乘上去就没有用了,从而没有了len不确定的影响

所以就是求是否有一段区间和大于等于0

把区间和转化为前缀和的差值,也就是s[r] - min(s[l - 1])

用一个变量存s[l - 1]的最小值即可,可以O(n)求

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int a[N], b[N], n, m, x, y;
double s[N];

bool check(double k, int *a, int n, int x)
{
    double res = -1e9, mi = 1e9;
    _for(i, 1, n) s[i] = s[i - 1] + a[i] - k;
    _for(i, 1, n)
    {
        if(i - x >= 0) mi = min(mi, s[i - x]);
        res = max(res, s[i] - mi);
    }
    return res >= 0;
}

double solve(int *a, int n, int x)
{
    double l = 0, r = 1e6;
    while(r - l > 1e-10)
    {
        double m = (l + r) / 2;
        if(check(m, a, n, x)) l = m;
        else r = m;
    }
    return l;
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &x, &y);
    _for(i, 1, n) scanf("%d", &a[i]);
    _for(i, 1, m) scanf("%d", &b[i]);
    printf("%.10f\n", solve(a, n, x) + solve(b, m, y));

    return 0;
}

Inverse Pair(思维)

当时是队友做的,我也写一下

每次加1,如果前面有a[i] + 1的那么逆序对可以减1,同时后面的a[i] - 1再加1就没用了

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
int a[N], f[N], vis[N], n;

int lowbit(int x) { return x & -x; }

void add(int x, int p)
{
    for(; x <= n; x += lowbit(x))
        f[x] += p;
}

int sum(int x)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        res += f[x];
    return res;
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);

    ll ans = 0;
    _for(i, 1, n)
    {
        ans += sum(n) - sum(a[i]);
        add(a[i], 1);

        if(!vis[a[i]] && sum(a[i] + 1) - sum(a[i]) == 1)
        {
            ans--;
            vis[a[i] - 1] = 1;
        }
    }
    printf("%lld\n", ans);

    return 0;
}

Tree Xor(异或  线段树写法 )

这题真的理解了我一个下午,太菜了

还是学到了很多很多的。有两种写法,一种是类似线段树,一种是字典树

可以发现,一个数的值确定了,其他数的值全部都确定了

如果设a[1] = 0,那么就可以求出其他所有点的权值

这时,如果令a[1] = x,那么其他所有点都会异或一个x

如a[i] = 0 ^ w1 ^ w2 ^ w3…… = w1 ^ w2 ^ w3……

a[1]=x以后,a[i] = x ^ w1 ^ w2 ^w3

因此会多异或一个x

那么问题就转化成a[1]有多少个取值x

使得 l[i] <= a[i] ^ x <= r[i] 对于每一个点都要符合

也就是a[i] ^ x 属于[l[i], r[i]]

两边同时异或一个a[i]

变成x属于[l[i], r[i]] ^ x

那么显然一个点可以贡献 (r[i] - l[i] - 1)个x的可能的取值

那么把所有点的可能的x的取值求交集,交集的个数就是答案

但是问题在于[l[i], r[i]] ^ x之后的值就很混乱了,不再是一个区间了

那么题解用了一个很巧妙的方法,用类似线段树的思路,将[l, r]分成很多个小区间

开始的区间取[0, 11111111111……] 也就是[0, (1 << 30) - 1]

这样子按照线段树去二分区间就会把[l, r]切成很多个小区间

这样的小区间都满足一个性质,l和r的高位是一样的,低位是0000和1111

如010101    000000

    010101    1111111 

非常神奇,更神奇的是,这样的区间去异或的话,结果还是落在一个区间里面

前面高位异或之后的结果是固定的,后面的0000到1111去异或之后,不论异或什么,因为已经取遍了所有情况,所以结果还是0000到1111

因此这种区间异或之后的结果还是一个区间

所以利用这个性质,就可以得出这样的小区间每一个数会出现一次

问题就转化为,有很多个区间,一个区间内的每一个数会出现一次

问有多少个数出现了n次

这就可以用一维差分来解决

实际上并没有用到线段树,只是用到了线段树二分区间的这个思想

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int l[N], r[N], a[N], n;
struct edge{ int v, w; };
vector<edge> g[N];
vector<pair<int, int>> res;

void dfs(int u, int fa)
{
    for(auto x: g[u])
    {
        if(x.v == fa) continue;
        a[x.v] = a[u] ^ x.w;           //先赋值再dfs 这个过程是向下搜的时候赋值,不是回溯的时候赋值
        dfs(x.v, u);
    }
}

void add(int l, int r, int pos, int x)
{
    int cut = ((1 << 30) - 1) ^ ((1 << pos) - 1);                 //切出pos到29位
    int L = (x & cut) ^ (l & cut); int R = L + (1 << pos) - 1;
    res.push_back(make_pair(L, 1));
    res.push_back(make_pair(R + 1, -1));
}

void split(int l, int r, int L, int R, int pos, int x) //分为第0位到第29位  pos~29位是一样的
{
    if(L <= l && r <= R)
    {
        add(l, r, pos, x);
        return;
    }
    int m = l + r >> 1;
    if(L <= m) split(l, m, L, R, pos - 1, x);
    if(R > m) split(m + 1, r, L, R, pos - 1, x);
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d%d", &l[i], &r[i]);
    _for(i, 1, n - 1)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back(edge{v, w});
        g[v].push_back(edge{u, w});
    }

    a[1] = 0;
    dfs(1, 0);
    _for(i, 1, n) split(0, (1 << 30) - 1, l[i], r[i], 30, a[i]);

    int cnt = 0, ans = 0;
    sort(res.begin(), res.end());
    rep(i, 0, res.size() - 1)
    {
        cnt += res[i].second;
        if(cnt == n) ans += res[i + 1].first - res[i].first;
    }
    printf("%d\n", ans);

    return 0;
}

周三

这几天晚上都在看奥运,没有写题,有点懈怠……训练起来!!

Tree Xor(二进制遍历)

学了这道题的另一种写法,我发现这种写法更好写,更普遍一些

我之前有遇到类似的思路

同样解决一个区间内的数异或的问题

首先对于区间[l, r] 用前缀和

把[0, r]的异或加入 

这里涉及到一个二进制的数的遍历方式

如一个二进制数100010101

可以从高位到低位,每遇到一个1,就把这一位赋值位0,后面可以为00000到11111

比如遇到第一个1,就可以切成一个区间

011111111

00000000

然后遇到第二个1,又可以切多一个区间

100001111

100000000

第三个1

100010011

100010000

这样就可以遍历完0到100010101

这样会少一个本身,还剩下一个自己,也就是100010101

可以发现,这样的一个一个区间又特殊的性质,前面是一样的,后面是全0到全1

这样的区间异或后还是一个区间,所以解决这个问题

具体写的时候还有一些细节要注意

1.因为不包括本身,所以计算这个数的时候加个1,就把本身加进去

2.写的时候巧用map,用一维差分

3.位运算

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int l[N], r[N], a[N], n;
struct edge{ int v, w; };
vector<edge> g[N];
map<int, int> mp;

void dfs(int u, int fa)
{
    for(auto x: g[u])
    {
        if(x.v == fa) continue;
        a[x.v] = a[u] ^ x.w;
        dfs(x.v, u);
    }
}

void add(int cur, int x, int v)
{
    for(int i = 30; i >= 0; i--)
    {
        int bit = 1 << i;
        if(cur & bit)
        {
            int cut = ~(bit - 1);              //把bit ~ 最高位 截下来
            int L = cut & (cur ^ bit ^ x);     //注意把当前位赋为0
            mp[L] += v;
            mp[L + bit] -= v;
        }
    }

}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d%d", &l[i], &r[i]);
    _for(i, 1, n - 1)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back(edge{v, w});
        g[v].push_back(edge{u, w});
    }

    a[1] = 0;
    dfs(1, 0);

    _for(i, 1, n)
    {
        add(r[i] + 1, a[i], 1);
        add(l[i], a[i], -1);
    }

    int ans = 0, pre = 0, cnt = 0;
    for(auto x: mp)
    {
        if(cnt == n) ans += x.first - pre;
        cnt += x.second;
        pre = x.first;
    }
    printf("%d\n", ans);

    return 0;
}

D - Meaningless Sequence(二进制遍历)

这道题之前用数位dp写的,也可以用上一道题的遍历一个二进制数来写

推一推公式就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int mod = 1e9 + 7;

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int binpow(int a, int b)
{
    int res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = mul(res, a);
        a = mul(a, a);
    }
    return res;
}

int main()
{
    string s;
    int c, len;

    cin >> s >> c;
    len = s.size();
    int ans = 0, cnt = 0;

    rep(i, 0, len)
        if(s[i] == '1')
        {
           ans = add(ans, mul(binpow(c + 1, len - i - 1), binpow(c, cnt)));
           cnt++;
        }
    ans = add(ans, binpow(c, cnt));
    printf("%d\n", ans);

    return 0;
}

P3384 【模板】轻重链剖分/树链剖分(树链剖分)

学学树链剖分

最近学一学树上问题的一些算法

树链剖分主要是维护树上路径的一些信息

和dfs序有点类似,都是把树形结构转化成线性结构

dfs序把一个子树拍成一个区间,而树链剖分可以把一条路径分成很多条链,每一条链都是连续的线性区间

具体实现重链剖分,对于一个节点,儿子中子树节点个数最多的为重儿子,父亲到重儿子的边叫重边

多条重边连成重链,重链的顶端是轻儿子。对于叶子节点,是轻儿子的话,这个单独的节点也视为一个重链

这样子下来一颗树就被剖成了很多条链

具体实现时要两次dfs

在dfs的时候,先搜重儿子,是的同一天重链的dfs序是连续的,这样才可以维护一条链的信息

在操作的时候,要处理一条路径上的信息的时候,同样是转成lca

这时类似树上倍增,这时点向上跳是一条链一条链的跳,可以证明这样是logn的

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int t[N << 2], lazy[N << 2], a[N], mod, root, n, m;
int d[N], siz[N], fa[N], son[N], top[N], id[N], rk[N], cnt;
vector<int> g[N];

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

void up(int k) { t[k] = add(t[l(k)], t[r(k)]); }

void update(int k, int l, int r, int x)
{
    lazy[k] = add(lazy[k], x);
    t[k] = add(t[k], mul(r - l + 1, x));
}

void down(int k, int l, int r)
{
    if(!lazy[k]) return;
    int m = l + r >> 1;
    update(l(k), l, m, lazy[k]);
    update(r(k), m + 1, r, lazy[k]);
    lazy[k] = 0;
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        t[k] = rk[l];
        return;
    }
    int m = l + r >> 1;
    build(l(k), l, m);
    build(r(k), m + 1, r);
    up(k);
}

void change(int k, int l, int r, int L, int R, int x)
{
    if(L <= l && r <= R)
    {
        update(k, l, r, x);
        return;
    }
    down(k, l, r);
    int m = l + r >> 1;
    if(L <= m) change(l(k), l, m, L, R, x);
    if(R > m)  change(r(k), m + 1, r, L, R, x);
    up(k);
}

int query(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k];
    down(k, l, r);
    int m = l + r >> 1, res = 0;
    if(L <= m) res = add(res, query(l(k), l, m, L, R));
    if(R > m) res = add(res, query(r(k), m + 1, r, L, R));
    return res;
}

void dfs1(int u, int father) //第一次dfs预处理,处理出深度,父亲,大小
{
    d[u] = d[father] + 1;
    fa[u] = father;
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void dfs2(int u, int fa, int t) //第二次dfs进行剖分,处理出dfs序,每条链的顶端,以及dfs序对应的权值
{
    top[u] = t;
    id[u] = ++cnt;
    rk[cnt] = a[u];

    if(siz[u] == 1) return;
    dfs2(son[u], u, t);

    for(int v: g[u])
    {
        if(v == fa || v == son[u]) continue;
        dfs2(v, u, v);
    }
}

void add(int u, int v, int x)
{
    while(top[u] != top[v]) //先以链为整体来考虑,不在同一条链的时候,让链顶端较深的点向上跳
    {
        if(d[top[u]] < d[top[v]]) swap(u, v);
        change(1, 1, n, id[top[u]], id[u], x); //跳的过程重处理这条链上的点的信息
        u = fa[top[u]];
    }
    if(d[u] < d[v]) swap(u, v);                 //在同一条链时,继续处理
    change(1, 1, n, id[v], id[u], x);           //从深度小的到深度深的,dfs序从小到大
}

int ask(int u, int v)
{
    int res = 0;
    while(top[u] != top[v])
    {
        if(d[top[u]] < d[top[v]]) swap(u, v);               //注意是比较top的深度,不是u,v的深度
        res = add(res, query(1, 1, n, id[top[u]], id[u]));
        u = fa[top[u]];
    }
    if(d[u] < d[v]) swap(u, v);
    res = add(res, query(1, 1, n, id[v], id[u]));         
    return res;
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &root, &mod);
    _for(i, 1, n) scanf("%d", &a[i]);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(root, 0);
    dfs2(root, 0, root);
    build(1, 1, n);

    while(m--)
    {
        int op, x, y, z;
        scanf("%d", &op);
        if(op == 1)
        {
            scanf("%d%d%d", &x, &y, &z);
            add(x, y, z);
        }
        else if(op == 2)
        {
            scanf("%d%d", &x, &y);
            printf("%d\n", ask(x, y));
        }
        else if(op == 3)
        {
            scanf("%d%d", &x, &z);
            change(1, 1, n, id[x], id[x] + siz[x] - 1, z);                 //用size数组推出dfs序的右端点
        }
        else
        {
            scanf("%d", &x);
            printf("%d\n", query(1, 1, n, id[x], id[x] + siz[x] - 1));
        }
    }

    return 0;
}

【模板】最近公共祖先(树链剖分求lca)

树链剖分的常数很小

求lca很方便

树链剖分说白了就是两次dfs剖成很多链,然后处理路径信息的时候往上一条链一条链的跳

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
int d[N], fa[N], siz[N], son[N], id[N], top[N], n, m, root, cnt;
vector<int> g[N];

void dfs1(int u, int father)
{
    d[u] = d[father] + 1;
    siz[u] = 1;
    fa[u] = father;
    for(int v: g[u])
    {
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    id[u] = ++cnt;
    top[u] = t;
    if(siz[u] == 1) return;
    dfs2(son[u], t);
    for(int v: g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

int lca(int u, int v)
{
    while(top[u] != top[v])
    {
        if(d[top[u]] < d[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return d[u] < d[v] ? u : v;
}

int main()
{
    scanf("%d%d%d", &n, &m, &root);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(root, 0);
    dfs2(root, root);

    while(m--)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        printf("%d\n", lca(u, v));
    }

    return 0;
}

P2184 贪婪大陆(树状数组)

开始以为要二维树状数组,发现空间开不下

然后发现只看不在当前区间就好了,和左端点和右端点有关

左端点和右端点分别维护一个树状数组即可

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int f[N][2], n, m, cnt;

int lowbit(int x) { return x & -x; }

void add(int x, int op)
{
    for(; x <= n; x += lowbit(x))
        f[x][op]++;
}

int sum(int x, int op)
{
    int res = 0;
    for(; x; x -= lowbit(x))
        res += f[x][op];
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    while(m--)
    {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if(op == 1)
        {
            add(x, 0);
            add(y, 1);
            cnt++;
        }
        else printf("%d\n", cnt - (sum(x - 1, 1) + sum(n, 0) - sum(y, 0)));
    }

    return 0;
}

周四

P2146 [NOI2015] 软件包管理器(树链剖分)

比较模板的一道题了

维护树上路径和子树的信息

写完一遍肉眼查错要仔细,检查一下低级错误,变量名打错等等

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int t[N << 2], lazy[N << 2], cnt, n, m;
int id[N], d[N], siz[N], son[N], fa[N], top[N];
vector<int> g[N];

void up(int k) { t[k] = t[l(k)] + t[r(k)]; }

void update(int k, int l, int r, int x)
{
    lazy[k] = x;
    t[k] = x * (r - l + 1);
}

void down(int k, int l, int r)
{
    if(lazy[k] == -1) return;
    int m = l + r >> 1;
    update(l(k), l, m, lazy[k]);
    update(r(k), m + 1, r, lazy[k]);
    lazy[k] = -1;
}

void change(int k, int l, int r, int L, int R, int x)
{
    if(L <= l && r <= R)
    {
        update(k, l, r, x);
        return;
    }
    down(k, l, r);
    int m = l + r >> 1;
    if(L <= m) change(l(k), l, m, L, R, x);
    if(R > m) change(r(k), m + 1, r, L, R, x);
    up(k);
}

int query(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k];
    down(k, l, r);
    int m = l + r >> 1, res = 0;
    if(L <= m) res += query(l(k), l, m, L, R);
    if(R > m) res += query(r(k), m + 1, r, L, R);
    return res;
}

void dfs1(int u, int father)
{
    siz[u] = 1;
    d[u] = d[father] + 1;
    fa[u] = father;
    for(int v: g[u])
    {
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    id[u] = ++cnt;
    top[u] = t;
    if(siz[u] == 1) return;
    dfs2(son[u], t);
    for(int v: g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

void modify(int u)
{
    while(top[u] != 1)
    {
        change(1, 1, n, id[top[u]], id[u], 1);
        u = fa[top[u]];
    }
    change(1, 1, n, id[1], id[u], 1);
}

int ask(int u)
{
    int res = 0;
    while(top[u] != 1)
    {
        res += query(1, 1, n, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    res += query(1, 1, n, id[1], id[u]);
    return res;
}

int main()
{
    memset(lazy, -1, sizeof lazy);
    scanf("%d", &n);
    _for(i, 2, n)
    {
        int x; scanf("%d", &x);
        x++;
        g[x].push_back(i);
    }
    dfs1(1, 0);
    dfs2(1, 1);

    scanf("%d", &m);
    while(m--)
    {
        char op[20]; int x;
        scanf("%s%d", op, &x);
        x++;
        if(op[0] == 'i')
        {
            printf("%d\n", d[x] - ask(x));
            modify(x);
        }
        else
        {
           printf("%d\n", query(1, 1, n, id[x], id[x] + siz[x] - 1));
           change(1, 1, n, id[x], id[x] + siz[x] - 1, 0);
        }
    }

    return 0;
}

P2486 [SDOI2011]染色(树链剖分 + 线段树)

用线段树维护左右端点的颜色以及颜色的段数

注意合并节点的时候lazy是不用理的,我开始写的时候lazy不小心写成上传了,调了巨久

lazy单独开一个数组

合并节点的时候注意边界处答案一样的话颜色的段数要减一

统计路径的答案的时候也要注意这个问题

#include <bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
struct node
{
    int sum, lc, rc, lazy;
    node(int a = 0, int b = 0, int c = 0, int d = 0): sum(a), lc(b), rc(c), lazy(d) {}
    node operator + (const node& rhs) const
    {
        if(!sum) return rhs;
        if(!rhs.sum) return *this;
        return node(sum + rhs.sum - (rc == rhs.lc), lc, rhs.rc, 0);
    }
}t[N << 2];
int id[N], d[N], siz[N], son[N], fa[N], top[N], color[N], rk[N];
int cnt, n, m;
vector<int> g[N];

void up(int k)
{
    t[k] = t[l(k)] + t[r(k)];
}

void update(int k, int x)
{
    t[k] = node{1, x, x, x};
}

void down(int k)
{
    if(!t[k].lazy) return;
    update(l(k), t[k].lazy);
    update(r(k), t[k].lazy);
    t[k].lazy = 0;
}

void build(int k, int l, int r)
{
    if(l == r)
    {
        t[k] = node(1, rk[l], rk[l], 0);
        return;
    }
    int m = l + r >> 1;
    build(l(k), l, m);
    build(r(k), m + 1, r);
    up(k);
}

void change(int k, int l, int r, int L, int R, int x)
{
    if(L <= l && r <= R)
    {
        update(k, x);
        return;
    }
    down(k);
    int m = l + r >> 1;
    if(L <= m) change(l(k), l, m, L, R, x);
    if(R > m) change(r(k), m + 1, r, L, R, x);
    up(k);
}

node query(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k];
    down(k);
    node res;
    int m = l + r >> 1;
    if(L <= m) res = res + query(l(k), l, m, L, R);
    if(R > m) res = res + query(r(k), m + 1, r, L, R);
    return res;
}

void dfs1(int u, int father)
{
    d[u] = d[father] + 1;
    fa[u] = father;
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    top[u] = t;
    id[u] = ++cnt;
    rk[cnt] = color[u];
    if(siz[u] == 1) return;
    dfs2(son[u], t);
    for(int v: g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

void modify(int u, int v, int c)
{
    while(top[u] != top[v])
    {
        if(d[top[u]] < d[top[v]]) swap(u, v);
        change(1, 1, n, id[top[u]], id[u], c);
        u = fa[top[u]];
    }
    if(d[u] < d[v]) swap(u, v);
    change(1, 1, n, id[v], id[u], c);
}

int ask(int u, int v)
{
    node resu, resv;
    while(top[u] != top[v])
    {
        if(d[top[u]] > d[top[v]])
        {
            resu = query(1, 1, n, id[top[u]], id[u]) + resu;
            u = fa[top[u]];
        }
        else
        {
            resv = query(1, 1, n, id[top[v]], id[v]) + resv;
            v = fa[top[v]];
        }
    }
    if(d[u] < d[v])
    {
        resv = query(1, 1, n, id[u], id[v]) + resv;
        return resv.sum + resu.sum - (resv.lc == resu.lc);
    }
    else
    {
        resu = query(1, 1, n, id[v], id[u]) + resu;
        return resv.sum + resu.sum - (resv.lc == resu.lc);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) scanf("%d", &color[i]);
    _for(i, 2, n)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1, 0);
    dfs2(1, 1);
    build(1, 1, n);

    while(m--)
    {
        char op[5]; int x, y, z;
        scanf("%s", op);
        if(op[0] == 'C')
        {
            scanf("%d%d%d", &x, &y, &z);
            modify(x, y, z);
        }
        else
        {
            scanf("%d%d", &x, &y);
            printf("%d\n", ask(x, y));
        }
    }

    return 0;
}

P3313 [SDOI2014]旅行(树链剖分)

开很多1e5颗线段树就好了

但是要动态开点,注意要引用

用root数组存一下每颗线段树的根

树剖就先练到这

感觉树剖就是个工具把树上路径转化为线性区间,用线段树维护

这几道题变化都在于线段树的写法

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int lc[N << 5], rc[N << 5], root[N];
struct node
{
    int sum, mx;
    node(int a = 0, int b = 0): sum(a), mx(b) {}
    node operator + (const node& rhs) const
    {
        return node(sum + rhs.sum, max(mx, rhs.mx));
    }
}t[N << 5];
int id[N], d[N], siz[N], son[N], fa[N], top[N], a[N], w[N], c[N];
int cnt, cnt2, n, m;
vector<int> g[N];

void add(int& k, int l, int r, int x, int p)
{
    if(!k) k = ++cnt2;
    if(l == r)
    {
        t[k] = node(p, p);
        return;
    }
    int m = l + r >> 1;
    if(x <= m) add(lc[k], l, m, x, p);
    else add(rc[k], m + 1, r, x, p);
    t[k] = t[lc[k]] + t[rc[k]];
}

node query(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k];
    node res;
    int m = l + r >> 1;
    if(L <= m) res = res + query(lc[k], l, m, L, R);
    if(R > m) res = res + query(rc[k], m + 1, r, L, R);
    return res;
}

void dfs1(int u, int father)
{
    d[u] = d[father] + 1;
    fa[u] = father;
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == father) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void dfs2(int u, int t)
{
    top[u] = t;
    id[u] = ++cnt;
    add(root[c[u]], 1, n, cnt, w[u]);
    if(siz[u] == 1) return;
    dfs2(son[u], t);
    for(int v: g[u])
    {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

node ask(int u, int v)
{
    int rt = root[c[u]];
    node res;
    while(top[u] != top[v])
    {
        if(d[top[u]] < d[top[v]]) swap(u, v);
        res = res + query(rt, 1, n, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    if(d[u] < d[v]) swap(u, v);
    res = res + query(rt, 1, n, id[v], id[u]);
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) scanf("%d%d", &w[i], &c[i]);
    _for(i, 2, n)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1, 0);
    dfs2(1, 1);

    while(m--)
    {
        string s; int x, y;
        cin >> s >> x >> y;

        if(s == "CC")
        {
            add(root[c[x]], 1, n, id[x], 0);
            add(root[y], 1, n, id[x], w[x]);
            c[x] = y;
        }
        else if(s == "CW")
        {
            add(root[c[x]], 1, n, id[x], y);
            w[x] = y;
        }
        else if(s == "QS") printf("%d\n", ask(x, y).sum);
        else printf("%d\n", ask(x, y).mx);
    }

    return 0;
}

下午眼睛有点痛,先不练了

明天练练启发式合并

这篇博客里有很多题目

https://blog.csdn.net/pb122401/article/details/84648993

周五

开刷树上启发式合并

看了几篇博客终于弄懂了,树上启发式合并就是优雅的暴力,把暴力的O(n^2)优化成O(nlogn)

用树上启发式合并有三个条件 1.统计子树内的一些信息 2.没有修改 3.离线

思考这样一个问题,一棵树,每个节点有颜色,问每一颗子树里有多少不一样的颜色

一看到题就可以dfs序 + 莫队 

但这是根号n的,树上启发式合并可以优化到log

先考虑暴力,暴力就是枚举每一个节点,然后对于一个节点就暴力统计其子树的颜色就好了

注意每次暴力之后要清空数组,避免影响其他子树的统计

代码是这样

void dfs2(int u, int fa)
{
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs2(v, u);
        clear(v, u);                              //暴力消除影响
    }
    
    insert(v, u);                                 //暴力统计当前子树答案
    
    for(auto x: ask[u]) ans[x.id] = sum[x.k];     //记录答案
}

这看起非常地暴力,树上启发式合并就是优化这个暴力的

可以发现,最后一个儿子是可以不用清空答案的,保留下来,当前子树统计答案的时候就不用统计它的,就统计其他儿子和根节点就行了

这个儿子显然越大越好,于是选择重儿子,即子树节点数最多的儿子。

这样竟然就可以优化到nlogn,这就是树上启发式合并

代码是这样

void dfs2(int u, int fa)
{
    for(int v: g[u])                              //1.先往下搜 保留重儿子的答案       
    {
        if(v == fa || v == son[u]) continue;      //先遍历轻儿子
        dfs2(v, u);
        clear(v, u);                              //遍历完暴力消除影响
    }
    if(son[u]) dfs2(son[u], u);                   //再遍历重儿子,不消除影响

    for(int v: g[u])                              //2.以下是统计当前节点答案
    {                                             //应该是直接insert u的,但是重儿子答案已有了,就统计剩下的就行了
        if(v == fa || v == son[u]) continue;      //剩下的就是轻儿子的和根节点的
        insert(v, u);                             //暴力统计轻儿子的答案
    }
    cnt[c[u]]++;                                  //加入根节点的答案
    sum[cnt[c[u]]]++;

    for(auto x: ask[u]) ans[x.id] = sum[x.k];     //3.记录当前的答案
}

CF375D Tree and Queries(树上启发式合并)

就是刚才讲的题加上一个统计大于等于k的颜色的数目

有一个很骚的操作,直接开一个sum数组记录,sum[i]表示大于等于i的颜色的数量,也就是直接记录答案

可以O(1)完成统计

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int son[N], siz[N], c[N], ans[N], cnt[N], sum[N], n, m;
struct node{ int id, k; };
vector<node> ask[N];
vector<int> g[N];

void dfs1(int u, int fa)
{
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void clear(int u, int fa)                        //把以u为根的子树影响清除
{
    sum[cnt[c[u]]]--;
    cnt[c[u]]--;
    for(int v: g[u])
    {
        if(v == fa) continue;
        clear(v, u);
    }
}

void insert(int u, int fa)                       //把以u为根的子树的答案加入
{
    cnt[c[u]]++;
    sum[cnt[c[u]]]++;
    for(int v: g[u])
    {
        if(v == fa) continue;
        insert(v, u);
    }
}

void dfs2(int u, int fa)
{
    for(int v: g[u])                              //1.先往下搜 保留重儿子的答案       
    {
        if(v == fa || v == son[u]) continue;      //先遍历轻儿子
        dfs2(v, u);
        clear(v, u);                              //遍历完暴力消除影响
    }
    if(son[u]) dfs2(son[u], u);                   //再遍历重儿子,不消除影响

    for(int v: g[u])                              //2.以下是统计当前节点答案
    {                                             //应该是直接insert u的,但是重儿子答案已有了,就统计剩下的就行了
        if(v == fa || v == son[u]) continue;      //剩下的就是轻儿子的和根节点的
        insert(v, u);                             //暴力统计轻儿子的答案
    }
    cnt[c[u]]++;                                  //加入根节点的答案
    sum[cnt[c[u]]]++;

    for(auto x: ask[u]) ans[x.id] = sum[x.k];     //3.记录当前的答案
}

void dfs2(int u, int fa)
{
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs2(v, u);
        clear(v, u);                              //暴力消除影响
    }
    
    insert(v, u);                                 //暴力统计当前子树答案
    
    for(auto x: ask[u]) ans[x.id] = sum[x.k];     //记录答案
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) scanf("%d", &c[i]);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    _for(i, 1, m)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        ask[u].push_back(node{i, k});
    }

    dfs1(1, 0);
    dfs2(1, 0);
    _for(i, 1, m) printf("%d\n", ans[i]);

    return 0;
}

E. Lomsat gelral(树上启发式合并)

这道题之前用线段树合并写过

貌似也可以用莫队写

这次用树上启发式合并写

思维量挺小的,因为真的很暴力

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int son[N], siz[N], c[N], cnt[N], mx, n;
vector<int> g[N];
ll sum, ans[N];

void dfs1(int u, int fa)
{
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs1(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void clear(int u, int fa)
{
    cnt[c[u]]--;
    for(int v: g[u])
    {
        if(v == fa) continue;
        clear(v, u);
    }
}

void add(int u)
{
    cnt[c[u]]++;
    if(cnt[c[u]] > mx)
    {
        mx = cnt[c[u]];
        sum = c[u];
    }
    else if(cnt[c[u]] == mx) sum += c[u];
}

void insert(int u, int fa)
{
    add(u);
    for(int v: g[u])
    {
        if(v == fa) continue;
        insert(v, u);
    }
}

void dfs2(int u, int fa)
{
    for(int v: g[u])
    {
        if(v == fa || v == son[u]) continue;
        dfs2(v, u);
        mx = sum = 0;
        clear(v, u);
    }
    if(son[u]) dfs2(son[u], u);

    for(int v: g[u])
    {
        if(v == fa || v == son[u]) continue;
        insert(v, u);
    }
    add(u);

    ans[u] = sum;
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &c[i]);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1, 0);
    dfs2(1, 0);
    _for(i, 1, n) printf("%lld ", ans[i]);

    return 0;
}

CF570D Tree Requests

这题我用了两种做法做

一.dfs序加前缀和

这是我看到这题的第一个想法,每一个深度存一个vector

之前做过类似的题

把点的dfs序打进去,然后可以每一个深度排序,然后做26个前缀和

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
int L[N], R[N], val[N], c[N], dmax, cnt, n, m;
struct record
{
    int sum[26];
    record() { memset(sum, 0, sizeof sum); }
};
vector<int> g[N], node[N];
vector<record> r[N];

void dfs(int u, int d)
{
    dmax = max(dmax, d);
    L[u] = ++cnt;
    val[cnt] = c[u];
    node[d].push_back(cnt);
    for(int v: g[u]) dfs(v, d + 1);
    R[u] = cnt;
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 2, n)
    {
        int x; scanf("%d", &x);
        g[x].push_back(i);
    }
    string s;
    cin >> s;
    rep(i, 0, n) c[i + 1] = s[i] - 'a';

    dfs(1, 1);
    _for(i, 1, dmax) sort(node[i].begin(), node[i].end());
    _for(i, 1, dmax)
    {
        record pre;
        for(int x: node[i])
        {
            record now = pre;
            now.sum[val[x]]++;
            r[i].push_back(now);
            pre = now;
        }
    }

    while(m--)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        if(k > dmax)
        {
            puts("Yes");
            continue;
        }
        int ll = lower_bound(node[k].begin(), node[k].end(), L[u]) - node[k].begin();
        int rr = upper_bound(node[k].begin(), node[k].end(), R[u]) - node[k].begin() - 1;
        if(ll > rr)
        {
            puts("Yes");
            continue;
        }

        int odd = 0;
        _for(i, 0, 25)
        {
            int cur = !ll ? r[k][rr].sum[i] : r[k][rr].sum[i] - r[k][ll - 1].sum[i];
            odd += cur % 2 == 1;
        }
        puts(odd <= 1 ? "Yes" : "No");
    }

    return 0;
}

二.树上启发式合并

很暴力

用cnt数组存一下每一个深度的每一个字母出现了多少次即可

不要再每个深度存一个vector,这样常数很大,会T

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
int c[N], d[N], siz[N], son[N], ans[N], cnt[N][30], n, m;
struct query { int id, k; };
vector<query> q[N];
vector<int> g[N];

void dfs1(int u, int dep)
{
    siz[u] = 1;
    d[u] = dep;
    for(int v: g[u])
    {
        dfs1(v, dep + 1);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void insert(int u)
{
    cnt[d[u]][c[u]]++;
    for(int v: g[u]) insert(v);
}

void clear(int u)
{
    cnt[d[u]][c[u]]--;
    for(int v: g[u]) clear(v);
}

void dfs2(int u)
{
    for(int v: g[u])
    {
        if(v == son[u]) continue;
        dfs2(v);
        clear(v);
    }
    if(son[u]) dfs2(son[u]);

    for(int v: g[u])
    {
        if(v == son[u]) continue;
        insert(v);
    }
    cnt[d[u]][c[u]]++;

    for(auto x: q[u])
    {
        int odd = 0;
        _for(i, 0, 25) odd += (cnt[x.k][i] % 2 == 1);
        ans[x.id] = (odd <= 1);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 2, n)
    {
        int x; scanf("%d", &x);
        g[x].push_back(i);
    }
    string s;
    cin >> s;
    rep(i, 0, n) c[i + 1] = s[i] - 'a';

    _for(i, 1, m)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        q[u].push_back(query{i, k});
    }

    dfs1(1, 1);
    dfs2(1);
    _for(i, 1, m) puts(ans[i] ? "Yes" : "No");

    return 0;
}

CF208E Blood Cousins

又想到了两种做法

一.dfs序 + 二分

首先要转化一下,用树上倍增迅速找出祖先,然后就转化成询问一个子树中有多少个深度为d的点

那么可以每一个深度建一个vector,把dfs序存进去

这个子树的关系就用dfs序来表示,然后vector 里面二分就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int vis[N], root[N], up[N][25], ans[N], dmax, n, m;
int L[N], R[N], cnt;
struct node { int id, dep; };
vector<int> g[N], ve[N];
vector<node> q[N], ask[N];

void dfs(int u, int fa, int depth)
{
    L[u] = ++cnt;
    ve[depth].push_back(cnt);
    dmax = max(dmax, depth);
    up[u][0] = fa;
    _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];

    for(auto x: q[u])
    {
        if(depth - x.dep < 1) continue;
        int cur = u;
        _for(j, 0, 20)
            if(x.dep & (1 << j))
                cur = up[cur][j];
        ask[cur].push_back(node{x.id, depth});
    }

    for(int v: g[u]) dfs(v, u, depth + 1);
    R[u] = cnt;
}

void dfs2(int u)
{
    for(auto x: ask[u])
    {
        int ll = lower_bound(ve[x.dep].begin(), ve[x.dep].end(), L[u]) - ve[x.dep].begin();
        int rr = upper_bound(ve[x.dep].begin(), ve[x.dep].end(), R[u]) - ve[x.dep].begin() - 1;
        ans[x.id] = rr - ll;
    }
    ask[u].clear();
    for(int v: g[u]) dfs2(v);
}

void solve(int rt)
{
    cnt = dmax = 0;
    dfs(rt, 0, 1);
    _for(i, 1, dmax) sort(ve[i].begin(), ve[i].end());
    dfs2(rt);
    _for(i, 1, dmax) ve[i].clear();
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        if(!x) root[i] = 1;
        else g[x].push_back(i);
    }
    scanf("%d", &m);
    _for(i, 1, m)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        q[u].push_back(node{i, k});
    }

    _for(i, 1, n)
        if(root[i])
            solve(i);
    _for(i, 1, m) printf("%d ", ans[i]);

    return 0;
}

二.树上启发式合并

对树上启发式合并有新的理解

前面那么做法是用dfs序来表示子树的关系

还可以用启发式合并表示查询子树内的信息,因为是直接暴力的

因此直接用一个数组维护当前不同深度有多少个点就好

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int vis[N], root[N], up[N][25], son[N], siz[N], ans[N], cnt[N], d[N], n, m;
struct node { int id, dep; };
vector<node> q[N], ask[N];
vector<int> g[N];

void dfs(int u, int fa, int depth)
{
    up[u][0] = fa;
    _for(j, 1, 20) up[u][j] = up[up[u][j - 1]][j - 1];
    d[u] = d[fa] + 1;

    for(auto x: q[u])
    {
        if(depth - x.dep < 1) continue;
        int cur = u;
        _for(j, 0, 20)
            if(x.dep & (1 << j))
                cur = up[cur][j];
        ask[cur].push_back(node{x.id, depth});
    }

    siz[u] = 1;
    for(int v: g[u])
    {
        dfs(v, u, depth + 1);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void insert(int u)
{
    cnt[d[u]]++;
    for(int v: g[u]) insert(v);
}

void clear(int u)
{
    cnt[d[u]]--;
    for(int v: g[u]) clear(v);
}

void dfs2(int u)
{
    for(int v: g[u])
    {
        if(v == son[u]) continue;
        dfs2(v);
        clear(v);
    }
    if(son[u]) dfs2(son[u]);

    for(int v: g[u])
    {
        if(v == son[u]) continue;
        insert(v);
    }
    cnt[d[u]]++;

    for(auto x: ask[u]) ans[x.id] = cnt[x.dep] - 1;
    ask[u].clear();
}

void solve(int rt)
{
    dfs(rt, 0, 1);
    dfs2(rt);
    clear(rt);
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        if(!x) root[i] = 1;
        else g[x].push_back(i);
    }
    scanf("%d", &m);
    _for(i, 1, m)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        q[u].push_back(node{i, k});
    }

    _for(i, 1, n)
        if(root[i])
            solve(i);
    _for(i, 1, m) printf("%d ", ans[i]);

    return 0;
}

CF246E Blood Cousins Return(树上启发式合并)

这题可以有多种方法

dfs序 + 树状数组

线段树合并

树上启发式合并

我发现树上启发式合并是思维量比较小的,代码量也不大

没啥吧思维吧,就是暴力

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
int root[N], son[N], siz[N], ans[N];
int cnt[N], d[N], n, m, dmax;
string name[N];

struct node { int id, k; };
vector<node> q[N];
vector<int> g[N];
map<string, int> mp[N];

void dfs(int u, int depth)
{
    dmax = max(dmax, depth);
    siz[u] = 1;
    d[u] = depth;
    for(int v: g[u])
    {
        dfs(v, depth + 1);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void insert(int u)
{
    if(++mp[d[u]][name[u]] == 1) cnt[d[u]]++;
    for(int v: g[u]) insert(v);
}

void clear(int u)
{
    if(--mp[d[u]][name[u]] == 0) cnt[d[u]]--;
    for(int v: g[u]) clear(v);
}

void dfs2(int u, int depth)
{
    for(int v: g[u])
    {
        if(v == son[u]) continue;
        dfs2(v, depth + 1);
        clear(v);
    }
    if(son[u]) dfs2(son[u], depth + 1);

    for(int v: g[u])
    {
        if(v == son[u]) continue;
        insert(v);
    }
    if(++mp[d[u]][name[u]] == 1) cnt[d[u]]++;

    for(auto x: q[u])
        if(depth + x.k <= dmax)
            ans[x.id] = cnt[depth + x.k];
}

void solve(int rt)
{
    dmax = 0;
    dfs(rt, 1);
    dfs2(rt, 1);
    clear(rt);
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x;
        cin >> name[i] >> x;
        if(!x) root[i] = 1;
        else g[x].push_back(i);
    }
    scanf("%d", &m);
    _for(i, 1, m)
    {
        int u, k;
        scanf("%d%d", &u, &k);
        q[u].push_back(node{i, k});
    }

    _for(i, 1, n)
        if(root[i])
            solve(i);
    _for(i, 1, m) printf("%d\n", ans[i]);

    return 0;
}

F. Dominant Indices(树上启发式合并)

没啥可说的,暴力

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
int son[N], siz[N], ans[N], cnt[N], d[N], n, mx, dep;
vector<int> g[N];

void dfs(int u, int fa)
{
    siz[u] = 1;
    d[u] = d[fa] + 1;
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        siz[u] += siz[v];
        if(siz[son[u]] < siz[v]) son[u] = v;
    }
}

void add(int u)
{
    cnt[d[u]]++;
    if(cnt[d[u]] > mx)
    {
        mx = cnt[d[u]];
        dep = d[u];
    }
    else if(cnt[d[u]] == mx)
        dep = min(dep, d[u]);
}

void insert(int u, int fa)
{
    add(u);
    for(int v: g[u])
    {
        if(v == fa) continue;
        insert(v, u);
    }
}

void clear(int u, int fa)
{
    cnt[d[u]]--;
    for(int v: g[u])
    {
        if(v == fa) continue;
        clear(v, u);
    }
}

void dfs2(int u, int fa, int depth)
{
    for(int v: g[u])
    {
        if(v == son[u] || v == fa) continue;
        dfs2(v, u, depth + 1);
        clear(v, u);
        mx = dep = 0;
    }
    if(son[u]) dfs2(son[u], u, depth + 1);

    for(int v: g[u])
    {
        if(v == son[u] || v == fa) continue;
        insert(v, u);
    }
    add(u);

    ans[u] = dep - depth;
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1, 0);
    dfs2(1, 0, 1);
    _for(i, 1, n) printf("%d\n", ans[i]);

    return 0;
}

周日

昨天打牛客了,今天补题

King of Range(单调队列)

首先可以发现,当l递增的时候,r是不降的

所以l和r的两个端点都是递增的,所以可以用单调队列O(n)的维护最大值和最小值

用set的话多一个log会T

单调队列的写法注意一下

取队里面的元素的时候要判断是否队列为空

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int a[N], qmin[N], qmax[N], n, m, k;
ll ans;

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) scanf("%d", &a[i]);
    while(m--)
    {
        ans = 0;
        scanf("%d", &k);

        int R = 0;
        int lmin = 1, rmin = 0;
        int lmax = 1, rmax = 0;
        _for(L, 1, n)
        {
            while(lmax <= rmax && qmax[lmax] < L) lmax++;
            while(lmin <= rmin && qmin[lmin] < L) lmin++;

            while(lmax > rmax || lmin > rmin || a[qmax[lmax]] - a[qmin[lmin]] <= k)
            {
                if(++R > n) break;
                while(lmax <= rmax && a[qmax[rmax]] <= a[R]) rmax--;
                while(lmin <= rmin && a[qmin[rmin]] >= a[R]) rmin--;
                qmax[++rmax] = qmin[++rmin] = R;
            }
            if(R > n) break;
            ans += n - R + 1;
        }

        printf("%lld\n", ans);
    }

    return 0;
}

Jewels(二分图最大权匹配)

队友想到了是二分图最大权匹配

抄了个KM模板过了

最大权匹配还可以转化为最小费用最大流来写

但是会慢一些,赛后我写然后T了

二分图的话,像最大匹配,多重匹配用网络流会快一些,但是最大权匹配就用KM算法更快

Boxes(数学期望)

策略挺好想,就是计算概率的时候我卡住了

可以设开几次箱结束,那么剩下的一定是全白或者全黑,前一个就是另一种颜色,剩下随便,就可以算出概率

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
double s[N], w[N], c, sum, ans;
int n;

int main()
{
    scanf("%d%lf", &n, &c);
    _for(i, 1, n)
    {
        scanf("%lf", &w[i]);
        sum += w[i];
    }
    sort(w + 1, w + n + 1);
    _for(i, 1, n) s[i] = s[i - 1] + w[i];

    double cur = 1;
    for(int i = n - 1; i >= 0; i--)
    {
        cur /= 2;
        ans += cur * s[i];
    }
    printf("%.9f\n", min(ans + c, sum));

	return 0;
}

Double Strings(dp + 组合数)

首先用dp求出A串中前i位,B串中前j位,有多少个相同的子序列

dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1]

在a[i] == b[j] 时再加上dp[i - 1][j - 1] + 1

处理完之后,当a[i] < b[j]时,第i位后面和第j位后面可以任意取相同的

这里用组合数,然后化简一下可以得到c(n, n + m) n是A串剩余的长度,m是B串剩余的长度

预处理一下阶乘和阶乘的逆元,这样就可以O(1)的算出组合数

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5000 + 10;
const int mod = 1e9 + 7;
int dp[N][N], fac[N << 1], invfac[N << 1], n, ans;
char a[N], b[N];

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int binpow(int a, int b)
{
    int res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = mul(res, a);
        a = mul(a, a);
    }
    return res;
}

int C(int n, int m)
{
    return mul(fac[m], mul(invfac[n], invfac[m - n]));
}

int main()
{
    invfac[0] = fac[0] = 1;
    _for(i, 1, 1e4)
    {
        fac[i] = mul(fac[i - 1], i);
        invfac[i] = binpow(fac[i], mod - 2);
    }

    scanf("%s%s", a + 1, b + 1);
    int lena = strlen(a + 1);
    int lenb = strlen(b + 1);

    _for(i, 1, lena)
        _for(j, 1, lenb)
        {
            dp[i][j] = (add(dp[i - 1][j], dp[i][j - 1]) - dp[i - 1][j - 1] + mod) % mod;
            if(a[i] == b[j]) dp[i][j] = add(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    _for(i, 1, lena)
        _for(j, 1, lenb)
            if(a[i] < b[j])
            {
                int t1 = lena - i, t2 = lenb - j;
                ans = add(ans, mul(dp[i - 1][j - 1] + 1, C(t1, t1 + t2)));
            }
    printf("%d\n", ans);

	return 0;
}

线性求逆

挺简单的,就是一个公式

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5000 + 10;
const int mod = 1e9 + 7;
int inv[N];

int main()
{
    inv[0] = inv[1] = 1;
    _for(i, 2, 5000) inv[i] = 1LL * (mod - mod / i) * inv[mod % i] % mod;

	return 0;
}

上面那题可以用线性求逆优化一下

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5000 + 10;
const int mod = 1e9 + 7;
int dp[N][N], fac[N << 1], invfac[N << 1], inv[N << 1], n, ans;
char a[N], b[N];

int add(int a, int b) { return (a + b) % mod; }
int mul(int a, int b) { return 1LL * a * b % mod; }

int C(int n, int m)
{
    return mul(fac[m], mul(invfac[n], invfac[m - n]));
}

int main()
{
    inv[0] = inv[1] = fac[0] = fac[1] = invfac[0] = invfac[1] = 1;
    _for(i, 2, 1e4)
    {
        fac[i] = mul(fac[i - 1], i);
        inv[i] = mul(mod - mod / i, inv[mod % i]);
        invfac[i] = mul(invfac[i - 1], inv[i]);
    }

    scanf("%s%s", a + 1, b + 1);
    int lena = strlen(a + 1);
    int lenb = strlen(b + 1);

    _for(i, 1, lena)
        _for(j, 1, lenb)
        {
            dp[i][j] = (add(dp[i - 1][j], dp[i][j - 1]) - dp[i - 1][j - 1] + mod) % mod;
            if(a[i] == b[j]) dp[i][j] = add(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    _for(i, 1, lena)
        _for(j, 1, lenb)
            if(a[i] < b[j])
            {
                int t1 = lena - i, t2 = lenb - j;
                ans = add(ans, mul(dp[i - 1][j - 1] + 1, C(t1, t1 + t2)));
            }
    printf("%d\n", ans);

	return 0;
}

树的直径

补一些这些小知识点,不难

树的直接就是树上最路径的最大值

有两种方法求

一种是两次dfs

第一次dfs找到距离根节点最长的点,这个点一定是直径的一端

然后再dfs一次就可以找到另外一端

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
vector<int> g[N];
int d[N], n, pos;

void dfs(int u, int fa)
{
    for(int v: g[u])
    {
        if(v == fa) continue;
        d[v] = d[u] + 1;
        if(d[v] > d[pos]) pos = v;
        dfs(v, u);
    }
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1, 0);
    d[pos] = 0;
    dfs(pos, 0);
    printf("%d\n", d[pos]);

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值