Educational Codeforces Round 7 部分题解

C. Not Equal on a Segment

脑子转不动,结果想了半天才做出来。。

有一个性质:\(a \geq b\)\(a \leq b \Leftrightarrow a = b\)

所以我们需要维护一个区间最小值 \(\mathrm{minn}\) 和区间最大值 \(\mathrm{maxn}\),如果 \(\mathrm{minn} = \mathrm{maxn} = x\) 即是无解。

这题还让我们随便输出一个不等于 \(x\) 的数的位置,既然 \(\mathrm{maxn}, \mathrm{minn}\) 至少有一个不等于 \(x\),那我们只需要输出那个数的位置就好了。

做法也很简单,使用线段树维护四个信息:区间最大值、最小值和区间最大值、最小值所在的位置。挺好写的。

时间复杂度 \(O(n \log n)\)

const int MAXN = 2e5 + 10;

int n, m;
int aa[MAXN];

namespace Segt {
    struct Node {
        int minn, maxn;
        int minp, maxp;
        Node() {minn = maxn = minp = maxp = 0;}
    } segt[MAXN << 2];

    #define ls (p << 1)
    #define rs (p << 1 | 1)

    Node Merge(Node pl, Node pr) {
        Node ret;
        if (pl.minn < pr.minn) { ret.minn = pl.minn; ret.minp = pl.minp; } 
        else { ret.minn = pr.minn; ret.minp = pr.minp; }
        if (pl.maxn > pr.maxn) { ret.maxn = pl.maxn; ret.maxp = pl.maxp; } 
        else { ret.maxn = pr.maxn; ret.maxp = pr.maxp; }
        return ret;
    }
    void Update(int p) {
        segt[p] = Merge(segt[ls], segt[rs]);
    }
    void buildTree(int p, int l, int r, int *aa) {
        if (l == r) {
            segt[p].minn = segt[p].maxn = aa[l];
            segt[p].minp = segt[p].maxp = l;
            return;
        } int mid = (l + r) >> 1;
        buildTree(ls, l, mid, aa); buildTree(rs, mid + 1, r, aa);
        Update(p);
    }
    Node Query(int p, int l, int r, int ll, int rr) {
        if (l == ll && rr == r) return segt[p];
        int mid = (l + r) >> 1;
        if (rr <= mid) return Query(ls, l, mid, ll, rr);
        else if (mid + 1 <= ll) return Query(rs, mid + 1, r, ll, rr);
        else return Merge(Query(ls, l, mid, ll, mid), Query(rs, mid + 1, r, mid + 1, rr));
    }
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n >> m;
    rep (i, 1, n) cin >> aa[i];
    Segt::buildTree(1, 1, n, aa);
    rep (i, 1, m) {
        int l, r, x; cin >> l >> r >> x;
        Segt::Node res = Segt::Query(1, 1, n, l, r);
        if (res.minn == x && res.maxn == x) puts("-1");
        else {
            if (res.minn == x) printf("%d\n", res.maxp);
            else printf("%d\n", res.minp);
        }
    }
    return 0;
}

实际上还有一种更简单的做法:求出每个位置往前走,第一个和它不同的数字在哪,然后先看右端点是不是和 x 相同,再看右端点往前走第一个和它不同的数字在不在左端点范围里。

const int MAXN = 2e5 + 10;

int n, m;
int aa[MAXN];
int prev[MAXN];

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    n = read(); m = read();
    rep (i, 1, n) {
        aa[i] = read();
        if (aa[i] == aa[i - 1]) prev[i] = prev[i - 1];
        else prev[i] = i - 1;
    }
    rep (i, 1, m) {
        int l = read(); int r = read(); int x = read();
        if (aa[r] != x) printf("%d\n", r);
        else if (prev[r] >= l) printf("%d\n", prev[r]);
        else puts("-1");
    }
    return 0;
}

D. Optimal Number Permutation

有趣的构造题。

考虑能不能把这个式子构造出 0,这种情况即是:\(\forall i \in [1, n - 1], d_i = n - i\),并且 \(d_n\) 可以是任意值。

例如 \(n = 6\) 时:

d1 = 5
d2 = 4
d3 = 3
d4 = 2
d5 = 1

接下来就胡乱手玩碰运气,尝试从小到大安排、从大到小安排,从前往后填、循环填……可以发现从小到大安排,循环找空填是一种似乎靠谱的构造方式:

1 3 5 5 3 1 2 4 6 4 2 6

\(1\)\(n - 1\),依次找第一个能放得下当前数字的位置填进去。可以发现这样的数字结构是两段回文数字拼起来,而且最后一定会剩下两个空,放入 \(n\) 即可。

const int MAXN = 5e5 + 10;

int n;
int ans[MAXN << 1];

void Solve() {
    int lenl = n, lenr = n - 1;
    int stl = 1, str = stl + lenl;
    int edl = stl + lenl - 1, edr = str + lenr - 1;
    for (int i = 1; i <= n - 1; i += 2) {
        int pos = (i + 1) >> 1;
        int ul = stl + pos - 1;
        int ur = edl - ul + 1;
        ans[ul] = ans[ur] = i;
    }
    for (int i = 2; i <= n - 1; i += 2) {
        int pos = i >> 1;
        int ul = str + pos - 1;
        int ur = edr - pos + 1;
        ans[ul] = ans[ur] = i;
    }
    for (int i = 1; i <= (n << 1); ++i) if (!ans[i]) ans[i] = n;
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> n;
    Solve(); // both compatible with odd and even
    rep (i, 1, (n << 1)) printf("%d ", ans[i]);
    puts("");
    return 0;
}

E. Ants in Leaves

很经典的题了。考虑贪心,对于所有根的儿子求出所有蚂蚁离开这个子树的最短时间,答案即是这些时间取 max。

怎么求每棵子树的最短时间?

考虑如果没有深度相同的点,那么答案就是最大深度,但是如果有了相同深度的点,这些相同深度的点就需要排队,所以不妨按顺序将它们的深度手动加一,深度加一就代表这个点需要多排一秒的队。这么一波处理完之后,答案即是最大深度。

const int MAXN = 5e5 + 10;

int n; std::vector<int> G[MAXN];

std::vector<int> depths;

void dfs(int u, int fa, int dep) {
    if (G[u].size() == 1) depths.push_back(dep);
    forall (G[u], i) {
        int v = G[u][i];
        if (v == fa) continue;
        dfs(v, u, dep + 1);
    }
}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    n = read();
    rep (i, 1, n - 1) {
        int u = read(); int v = read();
        G[u].push_back(v); G[v].push_back(u);
    }
    int ans = 0;
    forall (G[1], i) {
        int v = G[1][i];
        depths.clear();
        dfs(v, 1, 1);
        std::sort(ALL(depths));
        for (int i = 1; i < (int) depths.size(); ++i) {
            depths[i] = std::max(depths[i], depths[i - 1] + 1);
        }
        ans = std::max(ans, depths[depths.size() - 1]);
    } printf("%d\n", ans);
    return 0;
}

F

数学题……

posted @ 2021-10-26 20:31  Handwer  阅读(40)  评论(0编辑  收藏  举报