Codeforces Round #607 (Div. 1)

2021/4/17 18:55:12

本文主要是介绍Codeforces Round #607 (Div. 1),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Codeforces Round #607 (Div. 1)

A

每个位置一旦被赋值就不会再更改。记录当前哪些位置已经赋值,然后暴力更改没赋值的位置。但 \(m\) 之后的用不到不用管

B

答案只有 \(6\) 种

\(res=0\) 初始就全都是 \(A\)

\(res=1\) 矩阵的四条边界中有某条边界全是 \(A\)

\(res=2\) 某一行(列)全是 \(A\) 或者四个顶点中某个是 \(A\)

\(res=3\) 四条边界上有格子是 \(A\)

\(res=impossible\) 没有 \(A\)

\(res=4\) 除上述情况外

方案挺好构造的,自己 yy 吧

C

对每条边分开考虑,设这条边断开后树的两部分大小分别为 \(x,y\)

1、取最大值的时候这条边算了 \(min(x,y)\) 次

2、对于最小值,如果这条边经过了 \(\ge 2\) 次,这些点对为 \((x_1,y_1),(x_2,y_2)...\) 其中 \(x_i\) 属于同一部分,\(y_i\) 属于另一部分。那么不妨将 \(x_i,y_i\) 分别两两配对,更改为 \((x_1,x_2),(x_3,x_4)...(y_1,y_2),(y_3,y_4)...\)。这样更改后经过这条边的次数减少了,对于其他边也不会更劣。由此得出,如果 \(x\%2=1\),要算 \(1\) 次,否则经过 \(0\) 次

D

\(f_{i,j}\) 表示以 \(i\) 为根的子树中,分了 \(j\) 块时,符合条件的块数最大值\(i\) 所在块当前 \(w\) 和 \(b\) 差的最大值 (这是一个 \(pair\))

也就是说,\(dp\) 的时候用了一个贪心。可以这样理解,有两个最优条件:1、符合条件块数最多 2、当前差值最大

很明显,\(1\) 的优先级高于 \(2\),因为差值再大也只能把块数 \(+1\),所以先让 \(1\) 最优后再考虑 \(2\)

转移的时候就和树上背包一样枚举父亲和儿子的 \(j\)(第二维),分两种情况(父亲和儿子在不在一个块内)讨论,复杂度 \(O(n^2)\)

#include <bits/stdc++.h>
using namespace std;
#define int long long
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 = 3005, M = N << 1;
int n, m, a[N], b[N];
int cnt, h[N], nxt[M], to[M], sz[N];
void add (int u, int v) {
    to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
pair<int, int> f[N][N], t[N];
#define fi first
#define se second
void dfs (int u, int la) {
    sz[u] = 1; f[u][1] = {0, a[u]};
    for (int i = h[u], v; i; i = nxt[i]) {
        if ((v = to[i]) == la) continue;
        dfs (v, u);
        for (int j = 1; j <= sz[u] + sz[v]; ++j) t[j] = {-1, 0}; // 弄成-1因为有些状态不可达
        for (int j = 1; j <= sz[u]; ++j)
            for (int k = 1; k <= sz[v]; ++k) {
                t[j + k] = max (t[j + k], {f[v][k].fi + f[u][j].fi + (f[v][k].se > 0), f[u][j].se});
                t[j + k - 1] = max (t[j + k - 1], {f[v][k].fi + f[u][j].fi, f[v][k].se + f[u][j].se});
            }
        sz[u] += sz[v];
        for (int j = 1; j <= sz[u]; ++j) f[u][j] = t[j];
    }
}
signed main() {
    int T; read (T);
    while (T--) {
        read (n), read (m); cnt = 0;
        for (int i = 1; i <= n; ++i) h[i] = 0;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) f[i][j] = {0, 0};
        for (int i = 1; i <= n; ++i) read (b[i]);
        for (int i = 1; i <= n; ++i) read (a[i]), a[i] -= b[i];
        for (int i = 1, u, v; i < n; ++i)
            read (u), read (v), add (u, v), add (v, u);
        dfs (1, 0);
        printf ("%lld\n", f[1][m].fi + (f[1][m].se > 0));
    }
    return 0;
}

E

对于一个形态固定的电阻网络,设 \(g(R)\) 表示电路阻值为 \(R\) 时各个电阻阻值和的最小值。把这个带括号的序列对应到树上,要求的就是根节点的 \(g\) 函数关系式。更进一步,不难发现 \(g\) 其实是一个正比例函数,设 \(f(x)\) 为节点 \(x\) 处的 \(g\) 函数系数。如果没有整数的限制,考虑如何求得 \(f\)。

1、如果 \(x\) 节点的儿子是串联关系,显然,最优的办法是把所有的电阻都加在 \(f\) 最小的那个儿子上,其他放空。即 \(f_x=min(f_y)\)

2、如果是串联,列出两个式子,\(\frac{1}{R_x}=\sum\frac{1}{R_y},sum=\sum f_yR_y,y\in son(x)\),由第一个式子可得 \(R_x\sum\frac{1}{R_y}=1\),联想到“1”的妙用,把这个东西放入第二个式子:\(sum=R_x\sum\frac{1}{R_y}\sum f_yR_y=R_x\sum(\frac{1}{\sqrt{R_y}})^2\sum \sqrt{f_yR_y}^2\)。套用柯西不等式的取等条件,当取值最小时有 \(\frac{1}{\sqrt{f_yR_y^2}}=m\)(\(m\) 为定值)。那么 \(\frac{1}{R_y}\propto \sqrt{f_y}\),而 \(\sum\frac{1}{R_y}\) 为定值,那么 \(\frac{1}{R_y}\) 按照比例分配。可得 \(R_y=R_x\frac{\sum\sqrt{f_z}}{\sqrt{f_y}}\)。带入可得 \(\sqrt{f_x}=\sum\sqrt{f_y}\)

再回来看整数的限制

1、对于叶子节点有 \(f_x=1\),是完全平方数

2、对于第一种转移,如果 \(f_y\) 全是完全平方数那么 \(f_x\) 也一定是

3、对于第二种转移,\(f_y\) 是完全平方数,\(\sqrt{f_y}\) 是整数,\(\sqrt{f_x}\) 是整数,\(f_x\) 依然是一个完全平方数

那么,整数的限制作废了

计算具体阻值的时候可以按照构造方法往里面带,更简洁的做法:情况 \(1\) 的构造方法意味着多个串联的电阻中只会选一个。根据电路分析的基础知识可以发现,最后选出的电阻都可以看作并联关系,那么看一下有几个电阻用到然后...

#include <bits/stdc++.h>
using namespace std;
#define int long long
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 = 2e5 + 5, M = 5e5 + 5;
int n, dfn, st[N], tag[N], to[N], f[N], vis[N], id[N];
char a[M]; vector<int> g[N];
#define pb push_back
void build () {
    int num = 0, tp = 0;
    for (int i = 1; i <= n; ++i) {
        if (a[i] == '(') {
            g[++num].clear();
            if (tp) g[st[tp]].pb (num);
            st[++tp] = num;
            to[num] = vis[num] = f[num] = 0;
        } else if (a[i] == '*') {
            g[++num].clear();
            if (tp) g[st[tp]].pb (num);
            to[num] = vis[num] = f[num] = 0;
        }
        else if (a[i] == 'S') tag[st[tp]] = 0;
        else if (a[i] == 'P') tag[st[tp]] = 1;
        else if (a[i] == ')') --tp;
    }
}
void dfs (int u) {
    if (!g[u].size()) { f[u] = 1, id[u] = ++dfn; return; }
    for (int v : g[u]) dfs (v);
    if (tag[u] == 0) { // 串联直接找系数最小的
        int mn = 0;
        for (int v : g[u]) if (f[mn] > f[v]) mn = v;
        to[u] = mn, f[u] = f[mn];
    } else { // 串联直接加和
        for (int v : g[u]) f[u] += f[v];
    }
} int cnt;
void getcnt (int u) {
    if (!g[u].size()) { vis[id[u]] = 1, ++cnt; return; }
    if (!tag[u] && to[u]) getcnt (to[u]);
    else for (int v : g[u]) getcnt (v);
}
signed main() {
    int T; read (T);
    while (T--) {
        int R; scanf ("%lld", &R);
        cin.getline (a, 5e5); n = strlen (a + 1);
        build (); f[0] = 2e9;
        dfn = 0, dfs (1);
        cnt = 0; getcnt (1);
        printf ("REVOLTING ");
        for (int i = 1; i <= dfn; ++i)
            printf ("%lld ", vis[i] ? cnt * R : 0ll);
        putchar ('\n');
    }
    return 0;
}

F

这个游戏可以看成有三个可以转动的环,左右分别一个小的,整体是一个大的。说“转动”是因为把 \(E\) 沿着环转一圈相当于把换上的数字转动一个位置

何时无解?设 \(A\) 为按照行列顺次取数后得到的排列,怎样操作 \(A\) 的逆序对个数的奇偶性都不变,最终状态中显然没有逆序对,所以逆序对个数为奇数时无解。为偶数时如何构造答案?

我们规定 \(E\) 在第二行中间的位置时为标准状态,每次转动完依旧是标准状态。现在要做的就是把逆序对个数减少为 \(0\),然后再把 \(E\) 移到最右边

设第一行中间位置为 \(W\)。引进两个成套操作

1、对于左半部分的任意位置两个点 \(a,b\) 和右部任意一个点 \(c\)

转动左轮,使 \(b\) 到 \(W\)

转动右轮,使 \(c\) 到 \(W\)

转动左轮,使 \(a\) 到 \(W\)

转动右轮,使 \(b\) 到 \(W\),此时 \(a\) 到达原来 \(c\) 的位置

转动左轮,使 \(b\) 到原来 \(a\) 的位置,此时 \(c\) 一定在原来 \(b\) 的位置(距离相同)

这样,\((a,b,c)\) 成了 \((c,a,b)\),左一右二的类似

通过这个操作,可以把应该在左部的数换到左部,该在右边的扔到右边

2、对于左部两个点 \(a,b\),和右部两个点 \(c,d\),可以通过 \(2\) 次 \(1\) 操作将 \((a,b)(c,d)\) 变成 \((b,a)(d,c)\)

通过这个操作可以消除同一个部分之间的逆序对

具体的就不说了,咕咕咕



这篇关于Codeforces Round #607 (Div. 1)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程