2024.11.4 CW 模拟赛

题面 & 题解

T1

构造题.
这篇写的很好, 我就是听他给我讲懂的. %%%

#include "iostream"
#include "string"

using namespace std;

constexpr int N = 1e5 + 10, mod = 1e9 + 7;

int n, jc[N];
string s;

void init_jc()
{
    jc[0] = 1;
    for (int i = 1; i <= 1e5; ++i)
        jc[i] = 1ll * i * jc[i - 1] % mod;
    return;
}

void init()
{
    cin >> n;
    cin >> s;
    for (int i = 0; i ^ (n << 1); ++i)
    {
        if (s[i] == (i & 1 ? 'B' : 'W'))
            s[i] = '1';
        else
            s[i] = '0';
    }
    return;
}

void calculate()
{
    int cnt = 0, ans = jc[n];
    for (int i = 0; i ^ (n << 1); ++i)
    {
        if (s[i] == '0')
            ++cnt;
        else
            ans = 1ll * ans * cnt % mod, --cnt;
    }
    if (cnt ^ 0)
        ans = 0;
    cout << ans << '\n';
}

void solve()
{
    init();
    calculate();
}

int main()
{
    init_jc();
    solve();
    return 0;
}

T2

前言

话说输入卡我好几分钟.

算法

树链剖分, 线段树.

思路

在线不好做, 考虑离线.

离线后图会变成一个森林, 再对每一棵树进行剖分, 然后就是经典操作了.

注意需要边权转点权.

在输出时使用并查集维护连通性即可.

具体地, 如果在查询时两点不在同一联通块里, 就直接输出 -1 即可.

时间复杂度 \(\mathcal{O}(n \log^2 n)\).

#include "iostream"
#include "numeric"
#include "cstring"

using namespace std;

constexpr int N = 5e5 + 1;

int n, Q;
struct Node
{
    int u, v;
    bool id;
};
basic_string<Node> qus;
struct Edge
{
    int v, w, nxt;
} e[N << 1];
int head[N], idx = 0;

inline void add(int u, int v, int w)
{
    e[++idx] = {v, w, head[u]};
    head[u] = idx;
}

int dep[N], fa[N], son[N], sz[N], a[N];

void dfs1(int u, int f, int depth)
{
    dep[u] = depth;
    fa[u] = f, sz[u] = 1;
    int mxson = -1;
    for (int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].v, w = e[i].w;
        if (v == f)
            continue;
        dfs1(v, u, depth + 1);
        a[v] = w;
        sz[u] += sz[v];
        if (mxson < sz[v])
            mxson = sz[v], son[u] = v;
    }
}

int id[N], top[N], w[N], cnt = 0;

void dfs2(int u, int topf)
{
    id[u] = ++cnt;
    top[u] = topf;
    w[cnt] = a[u];
    if (son[u])
        dfs2(son[u], topf);
    for (int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].v;
        if (v == fa[u] or v == son[u])
            continue;
        dfs2(v, v);
    }
}

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

inline void merge(int x, int y)
{
    x = find(x), y = find(y);
    if (x ^ y)
        a[x] = y;
    return;
}

inline void read(int &x)
{
    x = 0;
    char ch = getchar();
    while (!isdigit(ch))
        ch = getchar();
    while (isdigit(ch))
        x = x * 10 + ch - 48, ch = getchar();
    return;
}

void init()
{
    read(n), read(Q);
    for (int k = 1; k <= Q; ++k)
    {
        int u, v, w = -1;
        string s;
        getline(cin, s);
        int len = s.size();
        if (!len)
            continue;
        for (int i = 0, val = 0, cnt = 0; i <= len; ++i)
        {
            if (isdigit(s[i]))
                val *= 10, val += s[i] - '0';
            else
            {
                if (!cnt)
                    u = val;
                else if (cnt == 1)
                    v = val;
                else if (cnt == 2)
                    w = val;
                ++cnt, val = 0;
            }
        }
        if (w == -1)
        {
            qus.push_back({u, v, 1});
        }
        else
        {
            qus.push_back({u, v, 0});
            add(u, v, w);
            add(v, u, w);
        }
    }
    for (int i = 1; i <= n; ++i)
        if (!id[i])
            dfs1(i, 0, 1), dfs2(i, i);
    return;
}

class Segment_Tree
{
protected:
    int p;
    int tr[N * 3];

public:
    void build()
    {
        p = 1;
        while (p <= n + 1)
            p <<= 1;
        for (int i = 1; i <= n; ++i)
            tr[p + i] = w[i];
        for (int i = p - 1; i; --i)
            tr[i] = tr[i << 1] + tr[i << 1 | 1];
        return;
    }

    inline int query_sum(int l, int r)
    {
        l = l + p - 1, r = r + p + 1;
        int ret = 0;
        while (l ^ 1 ^ r)
        {
            if (~l & 1)
                ret += tr[l ^ 1];
            if (r & 1)
                ret += tr[r ^ 1];
            l >>= 1, r >>= 1;
        }
        return ret;
    }

} sgt;

inline int query_sum(int x, int y)
{
    int ans = 0;
    while (top[x] ^ top[y])
    {
        if (dep[top[x]] < dep[top[y]])
            x ^= y ^= x ^= y;
        ans += sgt.query_sum(id[top[x]], id[x]);
        x = fa[top[x]];
    }
    if (dep[x] > dep[y])
        x ^= y ^= x ^= y;
    if (x ^ y)
        ans += sgt.query_sum(id[x] + 1, id[y]);
    return ans;
}

void calculate()
{
    sgt.build();
    iota(a + 1, a + n + 1, 1);
    for (auto [u, v, id] : qus)
    {
        if (!id)
        {
            merge(u, v);
            continue;
        }
        if (find(u) ^ find(v))
        {
            printf("-1\n");
            continue;
        }
        printf("%d\n", query_sum(u, v));
    }
    return;
}

void solve()
{
    init();
    calculate();
}

int main()
{
    solve();
    return 0;
}

不过 std 用的倍增求 LCA 来做, 时间复杂度 \(\mathcal{O}(n \log n)\), 值得学习一下.


T3

算法

二分图染色.

思路

这里有一个比较经典的思路, 就是建补图然后进行染色.

因为原图很难操作, 边也不好处理, 因此考虑建立补图.

根据补图的定义, 知道在补图中有一条边连接的两个节点在原图中不直接联通.

于是就把题目中 “团是一个两两之间有边的顶点集合” 转化为 “集合内的节点两两不直接联通”.

说到这里, 显然, 就变成将补图转化为一个二分图了.

需要注意, 在补图中可能存在若干个连通块, 所以我们需要对于每一个连通块都染一遍色.
特殊地, 如果存在奇环, 那么原图一定无解.
在一个二分图内, 试将其某一部点都染成白色, 另一部点都染成黑色. 而这两部点就是答案所求的两个团各自的一部分.
但是, 原图的补图会有多个二分图, 即不止一个连通块. 那如何确定每一个二分图左右部点各自所染的颜色呢?

可以得到, 最终答案的形式为 \(\frac{a(a-1)+b(b-1)}{2}\), 其中 \(a+b\) 为定值.
那么显然当 a, b 越接近 \(\frac{n}{2}\), 答案一定越大.

怎么实现呢?
这里采用暴力枚举.

正解是背包 dp.

这里放一个暴力代码.

#include "iostream"
#include "string"
#include "unordered_set"

using namespace std;

constexpr int N = 2e3 + 1;

int n, m;
char s[N];
basic_string<int> e[N];
bool f = 1;

void init()
{
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%s", s + 1);
        for (int j = 1; j <= n; ++j)
            if (s[j] == '0' and i ^ j) // 补图
                e[i].push_back(j);
    }
}

int cnt = 0;
int c[N], sum[N][3];

void color(int u, int fa = 0, int clr = 1)
{
    if (c[u])
    {
        if (c[u] ^ clr)
            f = 0;
        return;
    }
    c[u] = clr;
    ++sum[cnt][clr];
    for (int v : e[u])
    {
        if (v == fa)
            continue;
        color(v, u, clr == 1 ? 2 : 1);
    }
}

void calculate()
{
    for (int i = 1; i <= n; ++i)
        if (!c[i])
        {
            ++cnt;
            color(i);
        }
    if (!f)
        return puts("-1"), void();
    unordered_set<int> tmp, s;
    s.insert(0);
    for (int i = 1; i <= cnt; ++i)
    {
        for (int x : s)
            tmp.insert(x);
        for (int x : tmp)
        {
            s.erase(x);
            if (x + sum[i][1] <= (n >> 1))
                s.insert(x + sum[i][1]);
            if (x + sum[i][2] <= (n >> 1))
                s.insert(x + sum[i][2]);
        }
    }
    int ans = 1e9;
    for (int x : s)
        ans = min(ans, x * (x - 1) / 2 + (n - x) * (n - x - 1) / 2);
    printf("%d\n", ans);
}

void solve()
{
    init();
    calculate();
}

int main()
{
    solve();
    return 0;
}
posted @   Steven1013  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示