Loading [MathJax]/jax/output/CommonHTML/jax.js
W
H
X

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、对于最小值,如果这条边经过了 2 次,这些点对为 (x1,y1),(x2,y2)... 其中 xi 属于同一部分,yi 属于另一部分。那么不妨将 xi,yi 分别两两配对,更改为 (x1,x2),(x3,x4)...(y1,y2),(y3,y4)...。这样更改后经过这条边的次数减少了,对于其他边也不会更劣。由此得出,如果 x%2=1,要算 1 次,否则经过 0

D

fi,j 表示以 i 为根的子树中,分了 j 块时,符合条件的块数最大值i 所在块当前 wb 差的最大值 (这是一个 pair

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

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

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

#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 最小的那个儿子上,其他放空。fx=min(fy)

2、如果是串联,列出两个式子,1Rx=1Rysum=fyRyyson(x),由第一个式子可得 Rx1Ry=1,联想到“1”的妙用,把这个东西放入第二个式子:sum=Rx1RyfyRy=Rx(1Ry)2fyRy2。套用柯西不等式的取等条件,当取值最小时有 1fyR2y=mm 为定值)。那么 1Ryfy,而 1Ry 为定值,那么 1Ry 按照比例分配。可得 Ry=Rxfzfy带入可得 fx=fy

再回来看整数的限制

1、对于叶子节点有 fx=1,是完全平方数

2、对于第一种转移,如果 fy 全是完全平方数那么 fx 也一定是

3、对于第二种转移,fy 是完全平方数,fy 是整数,fx 是整数,fx 依然是一个完全平方数

那么,整数的限制作废了

计算具体阻值的时候可以按照构造方法往里面带,更简洁的做法:情况 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

转动左轮,使 bW

转动右轮,使 cW

转动左轮,使 aW

转动右轮,使 bW,此时 a 到达原来 c 的位置

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

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

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

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

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

具体的就不说了,咕咕咕

posted @   -敲键盘的猫-  阅读(64)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示