2025.8.7 Atto Round 1 (Codeforces Round 1041, Div. 1 + Div. 2)

比赛链接

Solved: 5/9


A. Mix Mex Max

题意

给一个数组 \(a\),某些数没填,判断能否使得对每个 \(i\) 都有 \(\text{mex}(a_i, a_{i+1}, a_{i+2}) = \max(a_i, a_{i+1}, a_{i+2}) - \min(a_i, a_{i+1}, a_{i+2})\)

题解

只有所有 \(a_i\) 都相等且不为 \(0\) 时可满足条件。

bool solve(){
    int n; cin >> n;
    vector <int> a(n); cin >> a;
    int x = -1;
    for (int i=0; i<n; ++i)
    {
        if (a[i] != -1) x = a[i];
    }
    if (x == 0) return 0;
    for (int i=0; i<n; ++i)
    {
        if (a[i] != -1 && a[i] != x) return 0;
    }
    return 1;
}

B. Hamiiid, Haaamid... Hamid?

题意

长为 \(n\) 的一维网格上有一些墙,一个人在第 \(x\) 个格子。A 和 B 玩游戏,每轮 A 可放置一面墙,B 可令人向左右任一方向移动到最近的一面墙并将其毁坏,若某方向没有墙则游戏结束。A 希望游戏轮数最少,B 希望轮数最多。求两人均最优决策时游戏轮数。

题解

最优决策下 B 每次选择的方向都相同。所以只需枚举第一步 A 和 B 的 4 种操作即可。

int solve(){
    int n, x; cin >> n >> x; --x;
    string a; cin >> a;
    int l = -1, r = n;
    for (int i=x-1; i>=0; --i) if (a[i] == '#') {l = i; break;}
    for (int i=x+1; i<n; ++i) if (a[i] == '#') {r = i; break;}
    return max(min(l+2,n-x), min(x+1,n-r+1));
}

C. Trip Shopping

题意

有两个序列 \(a\)\(b\),定义其权值为两个序列每两个元素差的绝对值之和。A 和 B 在玩游戏,每轮 A 可以指定两个标号 \(i\)\(j\),B 可以任意交换 \(a_i, b_i, a_j, b_j\) 四个数。A 希望权值最小,B 希望权值最大,一共进行 \(k\) 轮。求最终序列的权值。

题解

每轮操作一定会使序列权值不变或增大:无论 A 如何选择,B 都可以在能增大时增大,不能增大时不进行任何操作。

且由于交换的任意性,对于固定的指标 \((i,j)\),在经历一次交换后就达到了 B 角度下的最优情况。因此 A 的最优选择就是第一步选择增加量最小的指标,后面都和第一步选择相同指标(即浪费掉后面的轮数)。

将序列按 \(a\) 从小到大排序,增加量最小的指标一定是相邻两个。枚举即可。

void solve(){
    int n, k; cin >> n >> k;
    vector <pii> a(n);
    for (int i=0; i<n; ++i) cin >> a[i].first;
    for (int i=0; i<n; ++i) cin >> a[i].second;
    sort(all(a));
    ll ans = 0;
    for (int i=0; i<n; ++i) ans += abs(a[i].first - a[i].second);
    int mn = 2e9;
    for (int i=0; i<n-1; ++i)
    {
        vector <int> t = {a[i].first, a[i].second, a[i+1].first, a[i+1].second};
        sort(all(t));
        mn = min(mn, (t[3]-t[0]) + (t[2]-t[1]) - abs(a[i].first - a[i].second) - abs(a[i+1].first - a[i+1].second));
    }
    cout << ans + mn << '\n';
}

D. Root was Built by Love, Broken by Destiny

题意

给一张连通图,将其画成二分图使得任意两条边不相交。求本质不同的方案数。这里两个方案本质不同指的是存在一个点被分为不同恻,或者存在两个点被分为同侧但顺序不同。

题解

原图中只要存在环,分成二分图后就一定有边相交,答案为 0。因此只需考虑树的情形。

找到树的一条直径,则直径上每相邻两个点都是不同侧的。对于不在直径上的点,如果它们不是叶子,则子树内部的部分一定会与直径相交,此时答案也为 0。
而叶子的顺序可以任意交换,所以答案就是直径上的点(度数-2)的阶乘积。注意特判直径的第二个点和倒数第二个点,还要特判直径点数 \(\leq 3\) 的情况。

const int N = 2e5+5;
int n, m, x, y;
vector <int> e[N];
void adde(int x, int y)
{
    e[x].push_back(y);
}
bool fl, vis[N];
ll ans;
int dep[N], S, T, fa[N], deg[N];
bool on[N];
void dfs(int u, int f)
{
    dep[u] = dep[f] + 1;
    vis[u] = 1;
    if (dep[u] > dep[T]) T = u;
    fa[u] = f;
    for (int v : e[u]) if (v != f)
    {
        if (vis[v]) fl = 1;
        else dfs(v, u);
    }
}

ll solve(){
    cin >> n >> m;
    for (int i=1; i<=n; ++i) e[i].clear(), vis[i] = on[i] = deg[i] = 0;
    for (int i=0; i<m; ++i)
    {
        cin >> x >> y;
        adde(x,y), adde(y,x);
        ++deg[x], ++deg[y];
    }
    ans = 1; fl = 0; S = T = 0;
    dfs(1, 0);
    if (fl) return 0;
    S = T; T = 0;
    memset(vis, 0, sizeof(bool) * (n+1));
    dfs(S, 0);
    vector <int> D;
    for (int x = T; x != S; x = fa[x]) D.push_back(x), on[x] = 1;
    D.push_back(S); on[S] = 1;
    if (D.size() <= 2) return 2;
    else if (D.size() == 3) return fac[deg[D[1]]] * 2 % mod;
    else
    {
        ans = 4;
        for (int i=1; i<D.size()-1; ++i)
        {
            int x = D[i], cnt = 0;
            for (int y : e[x]) if (!on[y])
            {
                if (deg[y] > 1) return 0;
                ++cnt;
            }
            if (i != 1 && i != D.size()-2) ans = ans * fac[cnt] % mod;
            else ans = ans * fac[cnt+1] % mod;
        }
    }
    return ans;
}

E. Ancient Tree

题意

给一棵树,每个点有权值和颜色,某些点颜色未确定。定义树的权值为满足以下条件的点 \(v\) 的权值和:

  • 存在点 \(x,y\),使得 \(\text{lca}(x,y) = v, c_x = c_y \neq c_v\)

求树的权值的最小值,并给出一种染色方案。

题解

贪心地从下到上确定每个点的颜色。

\(x\) 的子树 \(v_1,v_2,\dots,v_k\) 中的颜色集合为 \(S_1,S_2,\dots,S_k\),令 \(T = \cup_{1\leq i<j\leq k} S_i\cap S_j\)

  • \(|T| = 0\),则 \(x\) 不可能产生贡献;
  • \(|T| = 1\),则令 \(x\) 的颜色等于这对点的颜色;
  • \(|T| \geq 2\),则 \(x\) 一定会产生贡献。

如果 \(x\) 还没确定颜色,则任取一个 \(\cup S_i\) 中颜色给 \(x\)

上述颜色集合可通过启发式合并/线段树合并等维护。最后会剩一些子树没确定颜色,直接从上到下再 dfs 一次,令这些没确定颜色的点的颜色与父亲相同,它们不会产生新的贡献。

const int N = 2e5+5;
int n, k, w[N], c[N], x, y;
vector <int> e[N];
void adde(int x, int y)
{
    e[x].push_back(y);
}
ll ans;
set <int> dfs1(int u, int f)
{
    set <int> U, T;
    if (u != 1 && e[u].size() == 1)
    {
        if (c[u]) U.insert(c[u]);
        return U;
    }
    for (int v : e[u]) if (v != f)
    {
        auto S = dfs1(v, u);
        if (U.size() < S.size()) swap(U, S);
        auto V = S;
        for (int x : S) if (U.find(x) == U.end()) V.erase(x);
        for (int x : V) T.insert(x);
        for (int x : S) U.insert(x);
    }
    T.erase(0);
    if (T.size() > 1) ans += w[u];
    else if (T.size() == 1)
    {
        int pos = *T.begin();
        if (!c[u]) c[u] = pos;
        else
        {
            if (c[u] != pos) ans += w[u];
        }
    }
    U.erase(0);
    if (!c[u])
    {
        if (U.size()) c[u] = *U.begin();
    }
    U.insert(c[u]);
    return U;
}
void dfs2(int u, int f)
{
    for (int v : e[u]) if (v != f)
    {
        if (!c[v]) c[v] = c[u];
        dfs2(v, u);
    }
}

void solve(){
    cin >> n >> k;
    for (int i=1; i<=n; ++i) e[i].clear();
    for (int i=1; i<=n; ++i) cin >> w[i];
    for (int i=1; i<=n; ++i) cin >> c[i];
    for (int i=1; i<n; ++i)
    {
        cin >> x >> y;
        adde(x, y), adde(y, x);
    }
    ans = 0;
    dfs1(1, 0);
    if (!c[1])
    {
        cout << "0\n";
        for (int i=1; i<=n; ++i) cout << 1 << " \n"[i==n];
        return;
    }
    dfs2(1, 0);
    cout << ans << '\n';
    for (int i=1; i<=n; ++i) cout << c[i] << " \n"[i==n];
}
posted @ 2025-08-08 03:12  EssnSlaryt  阅读(530)  评论(5)    收藏  举报