大二寒假第五周学习笔记

本文介绍了多种算法和数据结构的使用,包括平衡树(如splay、rope)、动态规划(如01背包、最大公约数)、点分治(求树上问题)和树上倍增(求树上最近公共祖先)。文章通过实例展示了如何在实际问题中应用这些技术,例如在弹珠游戏、货币系统、找最大中位数等问题上的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

全力以赴就会闪闪发光

周一

P5306 [COCI2019] Transport(点分治)

这题搞了好久……很精彩

首先这题的路径具有方向性,因此一条路径u到v要拆成两部分,u到lca,lca到v

先看u到lca的部分,多出来的油量很容易算出,关键是判断是否合法

如果要合法的话,显然是以u为起点的路径的油量最小值要大于等于0,就是合法的

这个怎么求呢,用dp求,类似一个序列求最大连续子序列和,dp[i]表示以i为结尾的连续子序列最大值

那么dp[i] = a[i] + min(dp[i - 1], 0)

那么这里类比到树上就可以求出了,dp[u] = a[u] - w + min(dp[fa], 0) w表示u与其父亲那条边的权值,dfs的过程用一个变量记录dp[fa]即可

如果dp[u] >= 0 就把当前可以多提供的油量存到L数组当中

然后看lca到v的部分,这部分简单一些,不用考虑合法性的问题。直接记录从lca到v的过程中最小的油量,这部分油量就是前一半需要提供的油量。注意lca这部分的油量是算到前一半里面的,细节。

因此先遍历当前子树,得到L,R数组,一条路径需要满足的条件是L >= R

因此将L,R排序后双指针即可,但是这里有一个问题,就是起点和终点需要在不同的子树,因此要容斥一下,存L,R数组时同时记录它属于哪颗子树,统计的时候要减去同一个子树的路径。

之前做的点分治题都是统计出当前子树然后马上统计答案,但是这样应用到这道题上会T,这道题统计出全部子树然后一起统计答案。

一般来说,solve函数的复杂度要控制在O(n)或O(nlogn) 这也是点分治的题的难点

最后注意lca本身也要算起点和终点

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

typedef long long ll;
const int N = 1e5 + 10;
vector<pair<int, int>> g[N];
int a[N], maxp[N], vis[N], siz[N], rt, sum, n, m, s;
vector<pair<ll, int>> L, R;
unordered_map<int, int> cnt;
ll ans;

void getrt(int u, int fa)
{
    siz[u] = 1; maxp[u] = 0;
    for(auto x: g[u])
    {
        int v = x.first, w = x.second;
        if(v == fa || vis[v]) continue;
        getrt(v, u);
        siz[u] += siz[v];
        maxp[u] = max(maxp[u], siz[v]);
    }
    maxp[u] = max(maxp[u], sum - siz[u]);
    if(maxp[u] < maxp[rt]) rt = u;
}

void get(int u, int fa, ll dis, ll mi, ll rmin, int id) //dis到目前的多余的油量 mi u为起点的最小油量 rmin 到达这个点过程中最低的油量
{
    if(mi >= 0) L.push_back({dis, id});
    R.push_back({-rmin, id});

    for(auto x: g[u])
    {
        int v = x.first, w = x.second;
        if(vis[v] || v == fa) continue;
        get(v, u, dis - w + a[v], a[v] - w + min(mi, 0LL), min(rmin, dis - a[s] - w), id);
    }
}

void solve(int u)
{
    L.clear(); R.clear();
    L.push_back({a[u], u}); cnt[u] = 0  //注意u本身为起点终点也要加入
    R.push_back({0, u});
    s = u;

    for(auto x: g[u])
    {
        int v = x.first, w = x.second;
        if(vis[v]) continue;
        cnt[v] = 0;
        get(v, u, a[u] - w + a[v], a[v] - w, -w, v);
    }

    sort(L.begin(), L.end());
    sort(R.begin(), R.end());

    int l = 0, r = -1;
    for(; l < L.size(); l++)
    {
        while(r + 1 < R.size() && R[r + 1].first <= L[l].first)
        {
            r++;
            cnt[R[r].second]++;
        }
        ans += r + 1 - cnt[L[l].second];
    }
}

void divide(int u)
{
    vis[u] = 1;
    solve(u);
    for(auto x: g[u])
    {
        int v = x.first;
        if(vis[v]) continue;
        maxp[rt = 0] = sum = siz[v];
        getrt(v, 0);
        getrt(rt, 0);
        divide(rt);
    }
}

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

    maxp[rt = 0] = sum = n;
    getrt(1, 0);
    getrt(rt, 0);
    divide(rt);

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

    return 0;
}

P3369 【模板】普通平衡树(splay)

学一学splay 码量巨大

是一种平衡树,可以维护排名为x的数,x的排名,x的前驱后继

板子用的是OIwiki的

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

const int N = 1e5 + 10;
int rt, tot, fa[N], ch[N][2], val[N], cnt[N], sz[N];

struct Splay
{
    void maintain(int x) { sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x]; } //重新计算x的sz值

    bool get(int x) { return x == ch[fa[x]][1]; }   //判断x是不是右儿子

    void clear(int x)                              //销毁x节点
    {
        ch[x][0] = ch[x][1] = fa[x] = val[x] = sz[x] = cnt[x] = 0;
    }

    void rotate(int x)                             //旋转操作 包含左旋和右旋
    {
        int y = fa[x], z = fa[y], chk = get(x);
        ch[y][chk] = ch[x][chk ^ 1];
        if(ch[x][chk ^ 1]) fa[ch[x][chk ^ 1]] = y;
        ch[x][chk ^ 1] = y;
        fa[y] = x; fa[x] = z;
        if(z) ch[z][y == ch[z][1]] = x;
        maintain(y);
        maintain(x);
    }

    void splay(int x) //将x旋转到根  每次访问x后都要splay
    {
        for(int f = fa[x]; f = fa[x]; rotate(x))
            if(fa[f]) rotate(get(x) == get(f) ? f : x);
        rt = x;
    }

    void ins(int k)   //插入操作
    {
        if(!rt)
        {
            val[++tot] = k;
            cnt[tot]++;
            rt = tot;
            maintain(rt);
            return;
        }

        int cur = rt, f = 0;
        while(1)
        {
            if(val[cur] == k)
            {
                cnt[cur]++;
                maintain(cur);
                maintain(f);
                splay(cur);
                break;
            }

            f = cur;
            cur = ch[cur][val[cur] < k];
            if(!cur)
            {
                val[++tot] = k;
                cnt[tot]++;
                fa[tot] = f;
                ch[f][val[f] < k] = tot;
                maintain(tot);
                maintain(f);
                splay(tot);
                break;
            }
        }
    }

    int rk(int k) //获取k的排名
    {
        int res = 0, cur = rt;
        while(1)
        {

            if(k < val[cur]) cur = ch[cur][0];
            else
            {
                res += sz[ch[cur][0]];
                if(k == val[cur])
                {
                    splay(cur);
                    return res + 1;
                }
                res += cnt[cur];
                cur = ch[cur][1];
            }
        }
    }

    int kth(int k)  //排名第k的是哪一个数
    {
        int cur = rt;
        while(1)
        {
            if(ch[cur][0] && k <= sz[ch[cur][0]]) cur = ch[cur][0];
            else
            {
                k -= cnt[cur] + sz[ch[cur][0]];
                if(k <= 0)
                {
                    splay(cur);
                    return val[cur];
                }
                cur = ch[cur][1];
            }
        }
    }

    int pre() //返回k的前驱 注意函数外要先插入再删除
    {
        int cur = ch[rt][0];
        if (!cur) return cur;
        while(ch[cur][1]) cur = ch[cur][1];
        splay(cur);
        return cur;
    }

    int nxt() //返回k的后继 注意函数外要先插入再删除
    {
        int cur = ch[rt][1];
        if (!cur) return cur;
        while(ch[cur][0]) cur = ch[cur][0];
        splay(cur);
        return cur;
    }

    void del(int k) //删除k 只删一个
    {
        rk(k);
        if(cnt[rt] > 1)
        {
            cnt[rt]--;
            maintain(rt);
            return;
        }
        if(!ch[rt][0] && !ch[rt][1])
        {
            clear(rt);
            rt = 0;
            return;
        }
        if(!ch[rt][0])
        {
            int cur = rt;
            rt = ch[rt][1];
            fa[rt] = 0;
            clear(cur);
            return;
        }
        if(!ch[rt][1])
        {
            int cur = rt;
            rt = ch[rt][0];
            fa[rt] = 0;
            clear(cur);
            return;
        }
        int cur = rt;
        int x = pre();
        fa[ch[cur][1]] = x;
        ch[x][1] = ch[cur][1];
        clear(cur);
        maintain(rt);
    }
}tree;


int main()
{
    int n;
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int op, x;
        scanf("%d%d", &op, &x);
        if (op == 1) tree.ins(x);
        if (op == 2) tree.del(x);
        if (op == 3) printf("%d\n", tree.rk(x));
        if (op == 4) printf("%d\n", tree.kth(x));
        if (op == 5) tree.ins(x), printf("%d\n", val[tree.pre()]), tree.del(x);
        if (op == 6) tree.ins(x), printf("%d\n", val[tree.nxt()]), tree.del(x);
    }
    return 0;
}

「HNOI2002」营业额统计(set/splay)

这道题就是求前驱和后继,其实这个可以用set,二分一下就可以了,没必要用平衡树

当然平衡树可以处理排名等更多的信息

set 和 map内部都是用平衡树实现的

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

typedef long long ll;
set<int> s;
int n, fi = 1;
ll ans;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        if(fi)
        {
            ans += x;
            fi = 0;
            s.insert(x);
            continue;
        }

        auto it = s.lower_bound(x);
        int cur = 1e9;

        if(it != s.begin())
        {
            it--;
            cur = min(cur, abs(x - *it));
            it++;
        }
        if(it != s.end()) cur = min(cur, abs(x - *it));
        ans += cur;

        s.insert(x);
    }
    printf("%lld\n", ans);

    return 0;
}

「HNOI2004」宠物收养所(set/splay)

和上一题差不多,求前驱后继即可。用set很方便

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

const int mod = 1e6;
set<int> s1, s2;
int n, ans;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int op, x;
        scanf("%d%d", &op, &x);
        if(op == 0)
        {
            if(s2.empty()) s1.insert(x);
            else
            {
                auto it = s2.lower_bound(x);
                int a = 1e9, b = 1e9;

                if(it != s2.begin())
                {
                    it--;
                    a = abs(x - *it);
                    it++;
                }
                if(it != s2.end()) b = abs(x - *it);

                if(a <= b)
                {
                    ans = (ans + a) % mod;
                    it--;
                    s2.erase(it);
                }
                else
                {
                    ans = (ans + b) % mod;
                    s2.erase(it);
                }
            }
        }
        else
        {
            if(s1.empty()) s2.insert(x);
            else
            {
                auto it = s1.lower_bound(x);
                int a = 1e9, b = 1e9;

                if(it != s1.begin())
                {
                    it--;
                    a = abs(x - *it);
                    it++;
                }
                if(it != s1.end()) b = abs(x - *it);

                if(a <= b)
                {
                    ans = (ans + a) % mod;
                    it--;
                    s1.erase(it);
                }
                else
                {
                    ans = (ans + b) % mod;
                    s1.erase(it);
                }
            }
        }
    }
    printf("%d\n", ans);

    return 0;
}

周三

P3391 【模板】文艺平衡树(rope)

rope这个STL可以用来解决一些平衡树的问题

它可以时间区间插入,删除,替换,都是logn

这道题而言就建立两个rope,一个正的一个反的,替换的时候就替换对应区间就可以了

用取子串相加这个操作

#include <bits/stdc++.h>
#include <ext/rope>
#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;
using namespace __gnu_cxx;

rope<int> a, b;
int n, m;

void solve(int l, int r)
{
    int ll = n - 1 - r, rr = n - 1 - l;
    rope<int> t = a;
    a = a.substr(a.begin(), a.begin() + l) + b.substr(b.begin() + ll, b.begin() + rr + 1) + a.substr(a.begin() + r + 1, a.end());
    b = b.substr(b.begin(), b.begin() + ll) + t.substr(t.begin() + l, t.begin() + r + 1) + b.substr(b.begin() + rr + 1, b.end());
}

int main()
{

    scanf("%d%d", &n, &m);
    _for(i, 1, n)
    {
        a.push_back(i);
        b.push_back(n - i + 1);
    }

    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        solve(l - 1, r - 1);
    }
    for(int x: a) printf("%d ", x);

    return 0;
}

P1537 弹珠(bitset优化)

不考虑优化的话,就是一道很裸的01背包,秒杀

#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 = 2e4 + 10;
int w[N], n, sum;
bool f[N];

int main()
{
    int kase = 0;
    while(1)
    {
        sum = n = 0;
        _for(i, 1, 6)
        {
            int x; scanf("%d", &x);
            while(x--) w[++n] = i, sum += i;
        }
        if(!sum) break;

        memset(f, 0, sizeof f);
        f[0] = 1;
        _for(i, 1, n)
            for(int j = sum; j >= w[i]; j--)
                f[j] |= f[j - w[i]];

        printf("Collection #%d:\n", ++kase);
        if(sum % 2 == 0 && f[sum / 2]) puts("Can be divided.");
        else puts("Can't be divided.");
        puts("");
    }

    return 0;
}

bitset可以优化bool数组的操作

看这行代码

        _for(i, 1, n)
            for(int j = sum; j >= w[i]; j--)
                f[j] |= f[j - w[i]];

如果将f数组看作一个二进制的话 每一位都和前面或,也就是整个和前面或,于是将f左移

等价于

_for(i, 1, n) f |= f << w[i];

bitset优化bool型的01背包比较常见

全部代码

#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 = 2e4 + 10;
int w[N], n, sum;
bitset<N> f;

int main()
{
    int kase = 0;
    while(1)
    {
        sum = n = 0;
        _for(i, 1, 6)
        {
            int x; scanf("%d", &x);
            while(x--) w[++n] = i, sum += i;
        }
        if(!sum) break;

        f.reset();
        f[0] = 1;
        _for(i, 1, n) f |= f << w[i];

        printf("Collection #%d:\n", ++kase);
        if(sum % 2 == 0 && f[sum / 2]) puts("Can be divided.");
        else puts("Can't be divided.");
        puts("");
    }

    return 0;
}

P5020 [NOIP2018 提高组] 货币系统(dp)

这题就是当时noip的题 当时没做出来

现在我读完题就会做了,一发AC……整个过程10分钟不到……

不就是个完全背包吗,就没了……

当时很努力,但是方法不对,总是看题解,很少自己思考,所以很菜。

现在方法好了些,自己思考比较多,比高中进步了很多了。

方法还是很重要,要常常思考怎么提高效率,而不是瞎努力,用蛮力。

#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 M = 2.5e4 + 10;
const int N = 110;
int w[N], n;
bool f[M];

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        _for(i, 1, n) scanf("%d", &w[i]);
        sort(w + 1, w + n + 1);

        int ans = 0;
        memset(f, 0, sizeof f);
        f[0] = 1;
        _for(i, 1, n)
        {
            if(f[w[i]]) continue;
            ans++;
            _for(j, w[i], w[n])
                f[j] |= f[j - w[i]];
        }
        printf("%d\n", ans);
    }

    return 0;
}

P3674 小清新人渣的本愿(莫队 + bitset优化)

这题妙啊

当bool数组的一些操作可以表示成位运算时,可以用二进制优化

用莫队来得出哪些数存在

a - b = x  即a = x + b

s储存哪些数存在,那么也就是s[k] = true  s[k + x] = true

表示成位运算就是s & (s << x)后至少有一个1  这里左移x和右移x效果是一样的

考虑加法,要做一个转化

a + b = x

a = x - b 即a = -b + x

那么可以储存-b  然后类似前面那样,移动x位

但是bitset显然不能存负数,所以我们需要一个偏置值d,使其为非负

因此等式两边同时+d

d + a = d - b + x

即(d - x) + a = (d - b)

用bitset存d - b   那么就移动d - x位就可以了

操作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;
const int d = 1e5;
bitset<N> s1, s2;
int a[N], ans[N], cnt[N], n, m;
struct query
{
    int op, l, r, x, bl, id;
}q[N];

bool cmp(query x, query y)
{
    if(x.bl != y.bl) return x.bl < y.bl;
    if(x.bl & 1) return x.r < y.r;
    return x.r > y.r;
}

void add(int x)
{
    x = a[x];
    if(++cnt[x] == 1) s1[x] = s2[d - x] = 1;
}

void del(int x)
{
    x = a[x];
    if(--cnt[x] == 0) s1[x] = s2[d - x] = 0;
}

int main()
{
    scanf("%d%d", &n, &m);
    int block = sqrt(n);
    _for(i, 1, n) scanf("%d", &a[i]);
    _for(i, 1, m)
    {
        scanf("%d%d%d%d", &q[i].op, &q[i].l, &q[i].r, &q[i].x);
        q[i].bl = q[i].l / block;
        q[i].id = i;
    }
    sort(q + 1, q + m + 1, cmp);

    int l = 1, r = 0;
    _for(i, 1, m)
    {
        int ll = q[i].l, rr = q[i].r, x = q[i].x, op = q[i].op;
        while(l < ll) del(l++);
        while(l > ll) add(--l);
        while(r < rr) add(++r);
        while(r > rr) del(r--);

        if(op == 1) ans[q[i].id] = (s1 & (s1 << x)).any();
        if(op == 2) ans[q[i].id] = (s1 & (s2 >> (d - x))).any();
        if(op == 3)
        {
            for(int j = 1; j * j <= x; j++)
                if(x % j == 0 && s1[j] && s1[x / j])
                {
                    ans[q[i].id] = 1;
                    break;
                }
        }
    }
    _for(i, 1, m) puts(ans[i] ? "hana" : "bi");

    return 0;
}

B. Maximum Value(筛法枚举 + 余数)

这题妙啊

关键是怎么枚举

ai % aj   设ai = aj * k + p

p = ai - k * aj  要最大 那么可以枚举k * aj

也就是在1e6的范围枚举每个数和每个数的倍数,这个复杂度是调和级数,nlogn的

对于每一个k * aj 考虑取什么ai可以使得p最大

在k * aj固定的情况下,要p尽可能大,那么ai要尽可能大,但是也要小于(k + 1) * aj

也就是在[1, (k + 1) * aj - 1]里面最大的数就可以了,所以我们可以预处理一个前缀最大值,就可以O(1)求得了

注意(k + 1) * aj - 1可能会爆空间,要和1e6取一个min

#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 pre[N], n;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int x; scanf("%d", &x);
        pre[x] = x;
    }
    _for(i, 1, 1e6) pre[i] = max(pre[i], pre[i - 1]);

    int ans = 0;
    for(int i = 1; i <= 1e6; i++)
        if(pre[i] == i)
            for(int j = i; j <= 1e6; j += i)
            {
                int cur = pre[min((int)1e6, j + i - 1)];
                if(cur > j) ans = max(ans, cur - j);
            }
    printf("%d\n", ans);

    return 0;
}

F. Ant colony(线段树)

秒切

每次询问一个区间,问有多少个数可以整除所有数

首先一个数可以整除所有数,意味着这个区间的gcd=min  同时还有问个数

所以用线段树维护区间的gcd,最小值,最小值的个数即可

同时维护值和个数用pair,写个合并函数,代码会简洁很多

#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;

typedef pair<int, int> pa;
const int N = 1e5 + 10;
int tgcd[N << 2], n, m;
pa t[N << 2];

int gcd(int a, int b) { return !b ? a : gcd(b, a % b); }

pa merge(pa a, pa b)
{
    if(a.first == b.first) return {a.first, a.second + b.second};
    if(a.first < b.first) return a;
    return b;
}

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

void build(int k, int l, int r)
{
    if(l == r)
    {
        int x; scanf("%d", &x);
        t[k] = {x, 1};
        tgcd[k] = x;
        return;
    }
    int m = l + r >> 1;
    build(l(k), l, m);
    build(r(k), m + 1, r);
    up(k);
}

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

pa ask_min(int k, int l, int r, int L, int R)
{
    if(L <= l && r <= R) return t[k];
    int m = l + r >> 1; pa res = {2e9, 0};
    if(L <= m) res = merge(res, ask_min(l(k), l, m, L, R));
    if(R > m) res = merge(res, ask_min(r(k), m + 1, r, L, R));
    return res;
}

int main()
{
    scanf("%d", &n);
    build(1, 1, n);
    scanf("%d", &m);
    while(m--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        int g = ask_gcd(1, 1, n, l, r);
        pa k = ask_min(1, 1, n, l, r);
        if(g == k.first) printf("%d\n", r - l + 1 - k.second);
        else printf("%d\n", r - l + 1);
    }

    return 0;
}

D. Omkar and Circle(观察)

这题我思路错了。我想的是贪心,每次删最小的数,写了优先队列+静态链表,比较麻烦,WA了……

发现原来是我理解错题意了,之后合并的数是不能再删的,因为不会再相邻了

于是删除的数一定是不相邻的,这是关键 用几个数随便删一下,发现剩下的数的和一定是只有一对相邻,然后都不相邻的

所以不论你怎么删,最终的答案是有规律的

于是我们可以枚举这个不相邻的位置,然后预处理出同奇偶的前缀后缀即可

#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;
ll a[N], b[N], ans;
int n;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        scanf("%lld", &a[i]), b[i] = a[i];
        if(i >= 3) a[i] += a[i - 2];
    }
    for(int i = n - 2; i >= 1; i--) b[i] += b[i + 2];
    _for(i, 1, n) ans = max(ans, a[i] + b[i + 1]);
    printf("%lld\n", ans);

    return 0;
}

周四

C. Ciel the Commander(点分治)

妙啊这题,用了点分治的思想

首先考虑A,发现只能放一个,如果再放一个A,那么路径上就没有大于A的

那么在一个点放一个A,就变成了在其每个子树里面放B到Z,显然是一个子问题

那么A给放哪了,显然越中间越好,这样子树的规模就比较小,更容易成功

“最中间”也就是树的重心,树的重心的最大子树最小。

于是就根据重心这样不断分解,也就是点分治的

树的重心的最大子树是小于等于n / 2的,所以每次问题规模都会减少一半,这样的话26个字母对于1e5这个数据是可以放完的,所以是一定可以的。

好好体会这个分治的思想

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

const int N = 1e5 + 10;
int siz[N], maxp[N], vis[N], n, rt, sum;
vector<int> g[N];
char ans[N];

void getrt(int u, int fa)
{
    maxp[u] = 0;
    siz[u] = 1;
    for(int v: g[u])
    {
        if(v == fa || vis[v]) continue;
        getrt(v, u);
        siz[u] += siz[v];
        maxp[u] = max(maxp[u], siz[v]);
    }
    maxp[u] = max(maxp[u], sum - siz[u]);
    if(maxp[u] < maxp[rt]) rt = u;
}

void divide(int u, int d)
{
    vis[u] = 1;
    ans[u] = d + 'A';
    for(int v: g[u])
    {
        if(vis[v]) continue;
        maxp[rt = 0] = sum = siz[v];
        getrt(v, 0);
        getrt(rt, 0);
        divide(rt, d + 1);
    }
}

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);
    }

    maxp[rt = 0] = sum = n;
    getrt(1, 0);
    getrt(rt, 0);
    divide(rt, 0);

    _for(i, 1, n) printf("%c\n", ans[i]);

    return 0;
}

F. Zero Remainder Sum(dp)

dp就行了,注意一些状态是不合法的,要初始化为最小值

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

const int N = 100;
int a[N][N], dp[N][N], f[N][N][N], t[N], n, m, k, lim;

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    _for(i, 1, n)
        _for(j, 1, m)
            scanf("%d", &a[i][j]);
    lim = m / 2;

    memset(dp, -0x3f, sizeof dp);
    dp[0][0] = 0;
    _for(i, 1, n)
    {
        memset(f, -0x3f, sizeof f);
        memset(t, -0x3f, sizeof t);
        f[0][0][0] = t[0] = 0;
        _for(j, 1, m)
            _for(r, 0, lim)
            {
                _for(p, 0, k - 1) f[j][r][p] = max(f[j][r][p], f[j - 1][r][p]);
                if(r > 0)
                {
                    _for(p, 0, k - 1)
                    {
                        int cur = (p + a[i][j]) % k;
                        f[j][r][cur] = max(f[j][r][cur], f[j - 1][r - 1][p] + a[i][j]);
                    }
                }
                _for(p, 0, k - 1) t[p] = max(t[p], f[j][r][p]);
            }

        _for(p1, 0, k - 1)
            _for(p2, 0, k - 1)
            {
                int cur = (p1 + p2) % k;
                dp[i][cur] = max(dp[i][cur], dp[i - 1][p1] + t[p2]);
            }
    }
    printf("%d\n", dp[n][0]);

    return 0;
}

C1. Errich-Tac-Toe (Easy Version)(棋盘染色)

之前做过把棋盘黑白染色使得没有相同的相邻的值的

这个类似,染三种颜色,这样任意三个相连的都有一种颜色

那么其实把一种颜色全部设为空就可以了

为了使次数小于等于k / 3 就要选择次数最小的颜色

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

const int N = 300 + 10;
char s[N][N];
int cnt[3], n;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(cnt, 0, sizeof cnt);
        scanf("%d", &n);
        _for(i, 1, n)
        {
            scanf("%s", s[i] + 1);
            _for(j, 1, n)
                if(s[i][j] == 'X')
                    cnt[(i + j) % 3]++;
        }

        int t = min_element(cnt, cnt + 3) - cnt;
        _for(i, 1, n)
        {
            _for(j, 1, n)
            {
                if(s[i][j] == 'X' && (i + j) % 3 == t) putchar('O');
                else putchar(s[i][j]);
            }
            puts("");
        }
    }

    return 0;
}

周五

E. Tree Painting(换根dp)

这题的关键是发现,只要起点定下来了,看作以其为根的树,可以发现接下来无论怎么染答案都是定下的,每次染色的答案一定是子树的个数

因为染当前点,子树肯定没染,父亲肯定染了

那么换根dp即可

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

typedef long long ll;
const int N = 2e5 + 10;
int siz[N], n, s, t;
vector<int> g[N];
ll ans;

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

void dfs2(int u, int fa, ll cur)
{
    ans = max(ans, cur);
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs2(v, u, cur + n - 2 * siz[v]);
    }
}


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, ans);
    printf("%lld\n", ans);

    return 0;
}

D. Max Median(二分答案)

这题妙啊

其实我很早就想到了二分答案,但是觉得没有单调性,大的答案成立不一定小的答案成立

但实际上换一种思考方式,就有单调性了

之前的二分答案的每次的check函数是答案为key符不符合

现在的check函数要改成存不存在大于等于key的答案,这样子就有单调性了

具体来说,一个序列,mid为中位数,那么大于等于mid的数的个数 > 小于mid的数的个数

所以当前为key,如果大于等于key的数的个数 > 小于key的数的个数,可以说明一定存在中位数mid >= key

根据这个二分答案

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

const int N = 2e5 + 10;
int a[N], b[N], n, k;

bool check(int key)
{
    _for(i, 1, n)
    {
        if(a[i] >= key) b[i] = 1;
        else b[i] = -1;
    }
    _for(i, 1, n) b[i] += b[i - 1];

    int mi = 0;
    _for(i, k, n)
    {
        if(b[i] - mi > 0) return true;
        mi = min(mi, b[i - k + 1]);
    }
    return false;
}

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

    int l = 1, r = n + 1;
    while(l + 1 < r)
    {
        int m = l + r >> 1;
        if(check(m)) l = m;
        else r = m;
    }
    printf("%d\n", l);

    return 0;
}

E. Minimum spanning tree for each edge(最小生成树+lca)

读完题就有一个非常直觉的想法,先跑最小生成树,一条边如果在这个生成树那就是这个答案,否则在原来的生成树上加上这一条边后就成了一个环,那么久删掉这个环上最大的那条边。

寻找这条边其实就是u到lca和v到lca的路径上最大的那条边,在求lca的时候顺便求出即可

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

typedef long long ll;
const int N = 2e5 + 10;
struct Edge
{
    int u, v, w, id, choose;
};
vector<Edge> e;
int f[N], d[N], up[N][20], mx[N][20], n, m;
vector<pair<int, int>> g[N];
ll ans[N];

bool cmp(Edge x, Edge y)
{
    return x.w < y.w;
}

int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }

void dfs(int u, int fa, int w)
{
    d[u] = d[fa] + 1;
    up[u][0] = fa;
    mx[u][0] = w;
    _for(j, 1, 19)
    {
        mx[u][j] = max(mx[u][j - 1], mx[up[u][j - 1]][j - 1]);
        up[u][j] = up[up[u][j - 1]][j - 1];
    }

    for(auto x: g[u])
    {
        if(x.first == fa) continue;
        dfs(x.first, u, x.second);
    }
}

int lca(int u, int v)
{
    int res = 0;
    if(d[u] < d[v]) swap(u, v);
    for(int j = 19; j >= 0; j--)
        if(d[up[u][j]] >= d[v])
        {
            res = max(res, mx[u][j]);
            u = up[u][j];
        }
    if(u == v) return res;
    for(int j = 19; j >= 0; j--)
        if(up[u][j] != up[v][j])
        {
            res = max(res, max(mx[u][j], mx[v][j]));
            u = up[u][j]; v = up[v][j];
        }
    return max(res, max(mx[u][0], mx[v][0]));
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n) f[i] = i;
    _for(i, 1, m)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        e.push_back({u, v, w, i, 0});
    }
    sort(e.begin(), e.end(), cmp);

    ll sum = 0;
    for(auto& x: e)
    {
        int u = find(x.u), v = find(x.v), w = x.w;
        if(u != v)
        {
            x.choose = 1;
            f[u] = v;
            sum += w;
            g[x.u].push_back({x.v, w});
            g[x.v].push_back({x.u, w});
        }
    }

    dfs(1, 0, 2e9);

    for(auto x: e)
    {
        if(x.choose)
        {
            ans[x.id] = sum;
            continue;
        }
        int t = lca(x.u, x.v);
        ans[x.id] = sum - t + x.w;
    }
    _for(i, 1, m) printf("%lld\n", ans[i]);

    return 0;
}

C. Ancient Berland Circus(三角形外接圆)

这题看题解的,计算几何写的很少

关键是从三角形外接圆考虑问题

知道三个边长可以求得外接圆半径

知道半径可以求得每条边对应的圆心角。多边形一定是内接在这个圆里面的

为了使面积小,就要边数小,也就是每个角对应的圆心角大,于是求三个圆心角的最大公约数。

知道了这个角,就可以求出一个三角形的面积,再乘以三角形个数

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

const double PI = acos(-1.0);

double fgcd(double a, double b) { return fabs(b) < 0.01 ? a : fgcd(b, fmod(a, b)); }

int main()
{
    double x1, y1, x2, y2, x3, y3;
    scanf("%lf%lf%lf%lf%lf%lf", &x1, &y1, &x2, &y2, &x3, &y3);
    double n1 = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));
    double n2 = sqrt(pow(x3 - x2, 2) + pow(y3 - y2, 2));
    double n3 = sqrt(pow(x1 - x3, 2) + pow(y1 - y3, 2));
    if(n1 > n2) swap(n1,n2);
    if(n2 > n3) swap(n2,n3);
    if(n1 > n2) swap(n1,n2);

    double p = (n1 + n2 + n3) / 2;
    double S = sqrt(p * (p - n1) * (p - n2) * (p - n3));
    double R = n1 * n2 * n3 / (4 * S);

    double angle1 = acos(1 - n1 * n1 / (2 * R * R));
    double angle2 = acos(1 - n2 * n2 / (2 * R * R));
    double angle3 = 2 * PI - angle1 - angle2;

    double angle = fgcd(angle1, fgcd(angle2, angle3));
    printf("%.6f\n", R * R * sin(angle) / 2 * (2 * PI / angle));

    return 0;
}

E. A and B and Lecture Rooms(树上倍增)

分类讨论一下就好了

注意,如果深度相同,那么lca的上方所有节点也是可以的。

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

const int N = 1e5 + 10;
int siz[N], d[N], up[N][20], n, q;
vector<int> g[N];

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

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

int lca(int u, int v)
{
    if(d[u] < d[v]) swap(u, v);
    for(int j = 19; j >= 0; j--)
        if(d[up[u][j]] >= d[v])
            u = up[u][j];
    if(u == v) return u;
    for(int j = 19; j >= 0; j--)
        if(up[u][j] != up[v][j])
            u = up[u][j], v = up[v][j];
    return up[u][0];
}

int jump(int u, int t)
{
    for(int j = 19; j >= 0; j--)
        if(t & (1 << j))
            u = up[u][j];
    return 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);

    scanf("%d", &q);
    while(q--)
    {
        int u, v;
        scanf("%d%d", &u, &v);

        int LCA = lca(u, v);
        int len = d[u] + d[v] - 2 * d[LCA];
        if(len % 2 == 1)
        {
            puts("0");
            continue;
        }

        int t = len / 2 - 1;
        if(d[u] == d[v]) printf("%d\n", siz[LCA] - siz[jump(u, t)] - siz[jump(v, t)] + n - siz[LCA]);
        else if(d[u] > d[v]) printf("%d\n", siz[jump(u, t + 1)] - siz[jump(u, t)]);
        else printf("%d\n", siz[jump(v, t + 1)] - siz[jump(v, t)]);
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值