Codeforces Round #715 (Div. 1)

Codeforces Round #715 (Div. 1)

Codeforces Round #715 (Div. 1)

A

三个串中必定存在两个串 A,B,满足max(min(number of 0 in A,number of 0 in B),min(number of 1 in A,number of 1 in B))n

可以反证如果不存在就很离谱

也就是说我们构造一个 S,使得 A,BS 的子序列,可以共用的部分 n,那么最终的长度 3×n

B

其实每个符合条件的序列可以被划分为若干段,以 i 为横坐标,ai 为纵坐标,就是这样:

每一段都是斜率为 1 的线段且相邻段首尾相接。那么如果长度和切割点一定,这个序列是唯一的。这就是一个简单的组合数。然后可以像进制转换那样去确定每一个切割点...

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); int f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 1e5 + 5, lim = 1e5, mx = 1e18;
int n, k, p[N], in[N], num[N];
signed main() {
    int T; read (T);
    int now = 1;
    for (int i = 1; i <= lim; ++i) {
        num[i] = now; now *= 2;
        if (now > mx) {
            for (int j = i + 1; j <= lim; ++j) num[j] = mx * 5;
            break;
        }
    } num[0] = 1;
    while (T--) {
        read (n), read (k);
        if (k > num[n]) { puts ("-1"); continue; }
        int p = 1, sum = 0, la = 0;
        for (int i = 1; i <= n; ++i) {
            sum += num[n - i];
            if (sum == k) {
                for (int j = i; j > la; --j) printf ("%lld ", j);
                for (int j = n; j > i; --j) printf ("%lld ", j); break;
            } else if (sum > k) {
                sum -= num[n - i];
                for (int j = i; j > la; --j) printf ("%lld ", j); la = i;
            }
        } puts ("");
    }
}

C

补图中只要一条边有权值 X 就可以满足异或为 0 的要求。先处理补图的连通性。如果有某条边不影响连通性,就把权值赋到这条边上,不用算入答案(不会出现在生成树中),然后把确定的边加入执行 Kruscal 的过程。否则(一棵树)要把 X 算入答案,然后先把确定边加入,再类似次小生成树处理一个替换调整的过程,可以跳 LCA 或再弄一个并查集来维护

如何处理补图的连通性?肯定存在一个点 p,度数 mn,那么补图中不与 p 直接相连的点 mn,对于这些点暴力判断,复杂度即为线性。也可以用一种边删边处理的方法解决。

D

(i,ai) 当成边,如果整个排列构成一个环,那么只需要随便选一个点,然后不断把当前点上的值归位即可,就像这样:

我们将其称之为“太阳线”,一个端点引出的线段显然没有交。

现在考虑多个环的情况,可以交换两个不同环上的两个点来合并成一个环。但交换会连出一些新的线,有可能会和“太阳线”相交。为了避免这种情况,我们先选定“中心点”,将其它所有点进行极角排序,对两个相邻点执行并查集的过程:如果在一个环中跳过,否则这两个环以这两点为媒介相连。容易发现这样构造不会和“太阳线”相交。

为了使极角排序简单顺利进行,不妨选最左点。

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); int f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 2020;
int n, p, fa[N], a[N], m, id[N], x[N], y[N]; double k[N];
int get (int x) {
    return x == fa[x] ? x : fa[x] = get (fa[x]);
}
void merge (int x, int y) { fa[get (x)] = get (y); }
bool cmp (int a, int b) { return k[a] < k[b]; }
struct ans { int x, y; } o[N * 10]; int cnt;
void work (int x, int y) {
    o[++cnt] = {x, y}, swap (a[x], a[y]);
}
signed main() {
    read (n); x[0] = 1e9;
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int i = 1; i <= n; ++i) {
        read (x[i]), read (y[i]);
        read (a[i]), merge (i, a[i]);
        if (i != a[i] && x[i] < x[p]) p = i;
    }
    if (!p) return puts ("0"), 0;
    for (int i = 1; i <= n; ++i)
        if (i != a[i] && i != p) id[++m] = i;
    for (int i = 1; i <= m; ++i) {
        k[id[i]] = 1.0 * (y[id[i]] - y[p]) / (x[id[i]] - x[p]);
    }
    sort (id + 1, id + m + 1, cmp);
    for (int i = 1; i < m; ++i) {
        int fx = get (id[i]), fy = get (id[i + 1]);
        if (fx == fy) continue;
        work (id[i], id[i + 1]), fa[fx] = fy;
    }
    while (a[p] != p) work (p, a[p]);
    printf ("%d\n", cnt);
    for (int i = 1; i <= cnt; ++i)
        printf ("%d %d\n", o[i].x, o[i].y);
    return 0;
}

E

每次操作会选一个 xson(x) 中权值最小的点 y 交换且 ax<ay,那么交换后 ay 依旧是最小的,那么操作不改变兄弟节点间的相对大小。借此可以确定树的 dfs

可以把操作分为若干轮,第 i 轮把数字 i 从根节点一路往最小的儿子向下走,直到走不了。由此可以得出当前进行的操作次数。当前正在进行的是 a11 轮,由此可以得出当前进行的操作次数为 1a11 数字所在节点的深度之和。剩下的问题是如何判断无解。

对于数字 [1,a12],操作已经结束,每个点所在的位置一定(可自行yy其规律,即代码中的 st 数组)

对于 a11 ,它的目标节点应当在当前所在节点的子树里,且它能够一直向上到根(即路径上权值都比它大)

对于 [a1,n] 所在的位置,它们的爸爸都得比自己小

#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
    char ch = getchar(); int f = 0; x = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 3e5 + 5, M = N << 1;
int n, s, a[N], w[N], tot, dfn[N], t[N], d[N], fa[N], o;
vector<pair<int, int> > g[N];
int cnt, h[N], nxt[M], to[M]; int tp, st[N];
void add (int u, int v) {
    to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
#define pb push_back
void dfs (int u, int la) {
    dfn[u] = ++tot, t[tot] = u, d[u] = d[la] + 1, fa[u] = la;
    for (int i = h[u], v; i; i = nxt[i])
        if ((v = to[i]) != la) g[u].push_back ({a[v], v});
    sort (g[u].begin(), g[u].end());
    for (auto i : g[u]) dfs (i.second, u); st[++tp] = u;
}
void YES () {
    printf ("YES\n%lld\n", s);
    for (int i = 1; i <= n; ++i) printf ("%lld ", dfn[i]);
}
void NO () { puts ("NO"), exit (0); }
void goup (int p) {
    int x = w[p];
    while (fa[x]) {
        if (a[x] > a[fa[x]]) NO ();
        swap (a[x], a[fa[x]]), x = fa[x];
    }
    x = st[p];
    while (x) { if (x == w[p]) return; x = fa[x]; }
    NO ();
}
signed main() {
    read (n);
    for (int i = 1; i <= n; ++i) read (a[i]), w[a[i]] = i;
    for (int i = 1, u, v; i < n; ++i)
        read (u), read (v), add (u, v), add (v, u);
    dfs (1, 0); o = a[1];
    for (int i = 1; i < o; ++i) s += d[w[i]] - 1;
    for (int i = 1; i < o - 1; ++i) if (w[i] != st[i]) NO ();
    if (o > 1) goup (o - 1);
    for (int i = 1; i <= n; ++i)
        if (a[fa[i]] > a[i] && a[i] >= o - 1) NO ();
    return YES (), 0;
}

F

考虑对于一个固定的 k 求出答案。先按照题意暴力的连边 (x,y) 表示限制 ax<ay。有这些个推导:

1、如果 u,v 之间存在长度 2 的路径,边 (u,v) 无用

2、对于一个 x,必须的边 (x,r),r>x 最多只有一条,左边亦然。因为如果有两条 (x,r1),(x,r2),x<r1<r2xr2 同在的区间也包含 r1,那么 r1,r2 间也有一条边或路径,根据结论 1(x,r1)(x,r2) 中有一条边无用

3、如何找到这个唯一的 l,r 呢?在所有包含 x 的区间中找到右端点 R 最大的,r 即为区间 (x,R]ax 的后继。l 亦然。

4、但结论 2 中唯一的 (x,r) 也不一定必要,可能存在路径 (x,,t,r) 并根据结论 1 去掉这条边。如何判断?容易发现这个 t 必定 <x,这次我们找到包含 r 的左端点 L 最小的区间,如果存在 p,满足 Lp<x,ax<ap<ar,那么这个 p 可以作为 t 而使 (x,r) 失效

以上四点可以让我们解决一个固定的 k 了:先用 3 求出所有 l,r,再用 4 判断哪些 l,r 无用,剩下的即为答案。但通过这种方法求出所有 k 对应的答案似乎太慢了。

In fact,稍稍改进一下结论和做法就可以较快速地求出所有答案。我们维护的 l,r 就是向左向右的后继,不妨顺便把前驱也计算出来,把这四条边称为 “判定边”。结论 4 引理:一条边 (x,y) 不会被删除当且仅当它同时是 x,y 的 “判定边”。可以自行证明其充要性。每个点的 “判定边” 的另一端单调,最多改变 O(n) 次,可以在 O(n2)/O(nq) 时间内维护(直接遍历 每个区间为 nq,先把不用更改的区间扔掉再遍历为 n2,差别不大),可以通过本题。

而官方题解的优秀做法就是处理一下每条边作为 “判定边” 的时间段然后 set+莫队 一顿操作弄到 O(nq logn)。反正特别麻烦,所以只写了非官方做法(下面的代码常数较大)

#include <bits/stdc++.h>
using namespace std;
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 25005;
int n, m, res, a[N], L[N], R[N], la[N][2], ra[N][2];
void work (int l, int r) {
    for (int i = l; i <= r; ++i) {
        for (int j = R[i] + 1; j <= r; ++j) {
            int t = ra[i][0];
            if (a[j] < a[i] && a[j] > a[t])
                res += (la[j][1] == i) - (la[t][1] == i), ra[i][0] = j;
            t = ra[i][1];
            if (a[j] > a[i] && a[j] < a[t])
                res += (la[j][0] == i) - (la[t][0] == i), ra[i][1] = j;
            R[i] = j;
        }
        for (int j = L[i] - 1; j >= l; --j) {
            int t = la[i][0];
            if (a[j] < a[i] && a[j] > a[t])
                res += (ra[j][1] == i) - (ra[t][1] == i), la[i][0] = j;
            t = la[i][1];
            if (a[j] > a[i] && a[j] < a[t])
                res += (ra[j][0] == i) - (ra[t][0] == i), la[i][1] = j;
            L[i] = j;
        }
    }
}
signed main() {
    read (n), read (m); a[n + 1] = n + 1;
    for (int i = 1; i <= n; ++i) read (a[i]);
    for (int i = 1; i <= n; ++i)
        R[i] = L[i] = i, la[i][1] = ra[i][1] = n + 1;
    for (int i = 1, l, r; i <= m; ++i) {
        read (l), read (r);
        work (l, r); printf ("%d\n", res);
    }
    return 0;
}

本文作者:-敲键盘的猫-

本文链接:https://www.cnblogs.com/whx666/p/715-div1.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   -敲键盘的猫-  阅读(71)  评论(0编辑  收藏  举报
编辑推荐:
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 现代计算机视觉入门之:什么是图片特征编码
阅读排行:
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· Spring AI + Ollama 实现 deepseek-r1 的API服务和调用
· 数据库服务器 SQL Server 版本升级公告
· C#/.NET/.NET Core技术前沿周刊 | 第 23 期(2025年1.20-1.26)
· 程序员常用高效实用工具推荐,办公效率提升利器!
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起