省选测试37

A 小A的树

题目大意 : 在一颗树中求所有点对的距离中最大的k个

  • 线段树维护直径可以在nlogn或nlogn2的时间内在一个区间内的数中找出离区间外一点距离最远的点

  • 于是可以把点对分成n类,第i类的是i与i之前的匹配,放到优先队列里找最远的点对,然后就可以把i点匹配的区间分成两半,然后再放进队列里

Code

Show Code
#include <queue>
#include <cstdio>
#define ls (rt << 1)
#define rs (rt << 1 | 1)

using namespace std;
typedef long long ll;
const int N = 2e5 + 5;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

struct Edge {
    int n, t, d;
}e[N*2];
int h[N], edc;

void Add(int x, int y, int z) {
    e[++edc] = (Edge) {h[x], y, z}; h[x] = edc;
}

int n, k, dep[N], fa[N], son[N], sz[N], tp[N];
ll d[N];

void Dfs(int x) {
    dep[x] = dep[fa[x]] + 1; sz[x] = 1;
    for (int i = h[x], y; i; i = e[i].n) {
        if ((y = e[i].t) == fa[x]) continue;
        fa[y] = x; d[y] = d[x] + e[i].d;
        Dfs(y); sz[x] += sz[y];
        if (sz[son[x]] < sz[y]) son[x] = y;
    }
}

void Dfs(int x, int top) {
    tp[x] = top;
    if (son[x]) Dfs(son[x], top);
    for (int i = h[x], y; i; i = e[i].n)
        if ((y = e[i].t) != fa[x] && y != son[x]) Dfs(y, y);
}

ll Dis(int x, int y) {
    ll ans = d[x] + d[y];
    while (tp[x] != tp[y])
        dep[tp[x]] > dep[tp[y]] ? x = fa[tp[x]] : y = fa[tp[y]];
    return ans - 2 * d[dep[x] < dep[y] ? x : y];
}

struct Tree {
    int x, y; ll d;
}t[N*4];

Tree operator + (const Tree &a, const Tree &b) {
    Tree c = a.d > b.d ? a : b;
    int x[] = {a.x, a.y}, y[] = {b.x, b.y};
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            ll d = Dis(x[i], y[j]);
            if (d > c.d) c = (Tree) {x[i], y[j], d};
        }
    }
    return c;
}

void Build(int rt, int l, int r) {
    if (l == r) return t[rt] = (Tree) {l, l}, void();
    int mid = l + r >> 1;
    Build(ls, l, mid); Build(rs, mid+1, r);
    t[rt] = t[ls] + t[rs];
}

Tree Ask(int rt, int l, int r, int x, int y) {
    if (x <= l && r <= y) return t[rt];
    int mid = l + r >> 1;
    if (y <= mid) return Ask(ls, l, mid, x, y);
    if (x >  mid) return Ask(rs, mid+1, r, x, y);
    return Ask(ls, l, mid, x, y) + Ask(rs, mid+1, r, x, y);
}

struct Node {
    int l, r, x, mid; ll d;
    Node() {}
    Node(int a, int b, int c) {
        l = a; r = b; x = c;
        Tree t = Ask(1, 1, n, l, r);
        ll d1 = Dis(x, t.x), d2 = Dis(x, t.y);
        if (d1 > d2) mid = t.x, d = d1;
        else mid = t.y, d = d2;
    }
};

bool operator < (const Node &a, const Node &b) {
    return a.d < b.d;
}

priority_queue<Node> q;

int main() {
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    n = read(); k = read();
    for (int i = 1; i < n; ++i) {
        int x = read(), y = read(), z = read();
        Add(x, y, z); Add(y, x, z);
    }
    Dfs(1); Dfs(1, 1); Build(1, 1, n);
    for (int i = 2; i <= n; ++i)
        q.push(Node(1, i-1, i));
    while (k--) {
        Node a = q.top(); q.pop();
        printf("%lld\n", a.d);
        int l = a.l, r = a.r, mid = a.mid, x = a.x;
        if (l < mid) q.push(Node(l, mid-1, x));
        if (mid < r) q.push(Node(mid+1, r, x));
    }
    return 0;
}

B 小B的序列

题目大意 : 区间取与,区间取或,区间查最大值

  • 线段树维护区间与和,或和,区间最大值,修改的时候,如果这个区间与和和或和改变了一样,就区间加变化值

  • 正确性好像挺显然,比如取与的时候,会把1变成0,如果这个区间里与和修改的地方和或和修改的地方一样,那么一定所有数都会改变一样的只

  • 时间复杂度的话不会证明,大概就是修改的时候会将区间变得更平?

Code

Show Code
#include <cstdio>
#include <algorithm>
#define ls (rt << 1)
#define rs (rt << 1 | 1)

using namespace std;
const int N = 2e5 + 5;

int read(int x = 0, int f = 1, char c = getchar()) {
    for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = -1;
    for (; c >='0' && c <='9'; c = getchar()) x = x * 10 + c - '0';
    return x * f;
}

int n, m, a[N];

struct Tree {
    int m, a, o, tag;
}t[N*4];

void Pushup(int rt) {
    t[rt].a = t[ls].a & t[rs].a;
    t[rt].o = t[ls].o | t[rs].o;
    t[rt].m = max(t[ls].m, t[rs].m);
}

void Update(int rt, int w) {
    t[rt].m += w; t[rt].a += w; t[rt].o += w; t[rt].tag += w;
}

void Pushdown(int rt) {
    if (!t[rt].tag) return;
    Update(ls, t[rt].tag);
    Update(rs, t[rt].tag);
    t[rt].tag = 0;
}

void Build(int rt, int l, int r) {
    if (l == r) return t[rt] = (Tree) {a[l], a[l], a[l]}, void();
    int mid = l + r >> 1;
    Build(ls, l, mid); Build(rs, mid+1, r);
    Pushup(rt);
}

void And(int rt, int l, int r, int x, int y, int w) {
    if (x <= l && r <= y && (w & t[rt].a) - t[rt].a == (w & t[rt].o) - t[rt].o) 
        return Update(rt, (w & t[rt].a) - t[rt].a);
    int mid = l + r >> 1; Pushdown(rt);
    if (x <= mid) And(ls, l, mid, x, y, w);
    if (y >  mid) And(rs, mid+1, r, x, y, w);
    Pushup(rt);
}

void Or(int rt, int l, int r, int x, int y, int w) {
    if (x <= l && r <= y && (w | t[rt].a) - t[rt].a == (w | t[rt].o) - t[rt].o) 
        return Update(rt, (w | t[rt].a) - t[rt].a);
    int mid = l + r >> 1; Pushdown(rt);
    if (x <= mid) Or(ls, l, mid, x, y, w);
    if (y >  mid) Or(rs, mid+1, r, x, y, w);
    Pushup(rt);
}

int Ask(int rt, int l, int r, int x, int y) {
    if (x <= l && r <= y) return t[rt].m;
    int mid = l + r >> 1, ans = 0; Pushdown(rt);
    if (x <= mid) ans = Ask(ls, l, mid, x, y);
    if (y >  mid) ans = max(ans, Ask(rs, mid+1, r, x, y));
    return ans;
}

int main() {
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    n = read(); m = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    Build(1, 1, n);
    while (m--) {
        int od = read(), l = read(), r = read();
        if (od == 1) And(1, 1, n, l, r, read());
        else if (od == 2) Or(1, 1, n, l, r, read());
        else printf("%d\n", Ask(1, 1, n, l, r));
    }
    return 0;
}

C 小C的利是 (Unaccepted)

题目大意 : 在n×n的阵中选n个不为-1的数,每行每列最多选1个数,问多少种选法可以使得选的数的和与k同余

  • 正解是行列式,但数据挺水,搜索到一定次数输出No就能过

Code

Show Code
posted @ 2021-03-19 19:07  Shawk  阅读(35)  评论(0编辑  收藏  举报