前言

wzy 在这场比赛中打到 rnk66,特此来纪念纪念。
由于细节和代码能力问题,wzy 就讲讲 EX 的思路(考场思路),代码就不放了(也没 AC)。
还有题意的话,大家就自己读了哈。

D - Magical Cookies

发现最对修改 2n 次,且每次修改是更改 n 个顶点,从而可以 bfs 模拟即可,直接上代码。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 2010;

char c[N][N];
int cl[N], cu[N];
int vis[N][N], visl[N], visu[N];
int cntl[N][27], cntu[N][27];
int suml[N], sumu[N];
queue<pair<int, int> > q;

int main() {
    int n, m; scanf ("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf ("%s", c[i] + 1);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            c[i][j] -= 'a'; int x = c[i][j];
            if (!cntl[i][x]) cl[i]++; cntl[i][x]++;
            if (!cntu[j][x]) cu[j]++; cntu[j][x]++;
            suml[i]++; sumu[j]++;
        }
    }
    for (int i = 1; i <= n; ++i) {
        if (!visl[i] && cl[i] == 1 && suml[i] >= 2) q.push (make_pair (i, 0)), visl[i] = 1;
    }
    for (int i = 1; i <= m; ++i) {
        if (!visu[i] && cu[i] == 1 && sumu[i] >= 2) q.push (make_pair (i, 1)), visu[i] = 1;
    }
    while (!q.empty()) {
        pair<int, int> u = q.front(); q.pop();
        if (u.second == 0) {
            int i = u.first;
            for (int j = 1; j <= m; ++j) {
                if (vis[i][j]) continue;
                int x = c[i][j]; vis[i][j] = 1;
                suml[i]--; sumu[j]--;
                cntl[i][x]--; if (!cntl[i][x]) cl[i]--;
                cntu[j][x]--; if (!cntu[j][x]) cu[j]--;
            }
        } else {
            int j = u.first;
            for (int i = 1; i <= n; ++i) {
                if (vis[i][j]) continue;
                int x = c[i][j]; vis[i][j] = 1;
                suml[i]--; sumu[j]--;
                cntl[i][x]--; if (!cntl[i][x]) cl[i]--;
                cntu[j][x]--; if (!cntu[j][x]) cu[j]--;
            }
        }
        for (int i = 1; i <= n; ++i) {
            if (!visl[i] && cl[i] == 1 && suml[i] >= 2) q.push (make_pair (i, 0)), visl[i] = 1;
        }
        for (int i = 1; i <= m; ++i) {
            if (!visu[i] && cu[i] == 1 && sumu[i] >= 2) q.push (make_pair (i, 1)), visu[i] = 1;
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (!vis[i][j]) ans++;
        }
    }
    printf ("%d\n", ans);
    return 0;
}

E - Prerequisites

建反边,看节点 1 的前驱,然后 dfs 直接扫描即可。

#include <queue>
#include <cstdio>
#include <iostream>
using namespace std;

const int N = 2e5 + 10;

struct node {
    int to, nxt;
} edge[N << 1];
int cnt, head[N], in[N];
int n, ans[N], cc[N], vis[N];

void add (int u, int v) {
    cnt ++;
    edge[cnt].to = v;
    edge[cnt].nxt = head[u];
    head[u]= cnt;
}

void dfs1 (int u) {
    vis[u] = 1;
    for (int i =head[u]; i; i = edge[i].nxt) {
        int v = edge[i].to; cc[v]++;
        if (!vis[v]) dfs1(v);
    }
}

void dfs (int u) {
    ans[++cnt] = u;
    for (int i = head[u]; i; i = edge[i].nxt) {
        int v = edge[i].to; in[v]++;
        if (in[v] == cc[v]) dfs(v);
    }
}

int main() {
    scanf ("%d", &n);
    for (int i = 1; i <= n; ++i) {
        int c; scanf ("%d", &c);
        for (int j = 1; j <= c; ++j) {
            int p; scanf ("%d", &p); add(i, p);
        }
    } cnt = 0; dfs1(1); dfs(1);
    for (int i = cnt; i >= 1; --i) {
        if (ans[i]== 1) break;
        printf ("%d ", ans[i]);
    }
    return 0;
}

F - Shortcuts

任意两点间的距离 $\leq 20000$,所以每个站点都去的最大代价是 $10^4 \times 20000 = 2\times 10^8$。
假定最优解 $C + 1$ 个站点不去,会花费 $2^C $ 钱,所以满足 $C \leq log_2(2 \times 10^8) $,即 $C \leq 30$(其实 20 就够了,大家自己可以想一想,不过没啥影响)。
这样就很好做了,直接 dp 即可。设 $f[i][j]$ 表示到第 $i$ 个站点(不能跳过),一共跳过 $j$ 个站点的最小代价。
转移方程考虑上一个不被跳过的站点即可,看代码即可。

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;

const int M = 30;
const int N = 1e4 + 10;

double f[N][20];
int x[N], y[N];

double solve (int a, int b) {
    return sqrt ((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}

int main() {
    int n; scanf ("%d", &n);
    for (int i = 1; i <= n; ++i) scanf ("%d%d", &x[i], &y[i]);
    double ans = 0;
    f[1][0] = 0; f[1][1] = 1e9;
    for (int i = 2; i <= n; ++i) {
        for (int j = 0; j <= 20; ++j) {
            f[i][j] = 1e9;
            for (int k = i - 1; k >= max (1, i - j - 1); --k) {
                f[i][j] = min (f[i][j], f[k][j - (i - k - 1)] + solve (k, i));
            }
        }
    }
    ans = f[n][0];
    for (int i = 1; i <= 20; ++i) {
        ans = min (ans, (1 << (i - 1)) + f[n][i]);
    }
    printf ("%.20lf\n", ans);
    return 0;
}

G - Ai + Bj + Ck = X (1 <= i, j, k <= N)

考虑到这个式子非常像扩欧的式子,于是考虑枚举 i, 那现在的目标就是求 $Bj + Ck = X - Ai(1 \leq j, k \leq n)$,这样就可以直接扩欧了。
具体关于上取整,下取整的细节见代码。

#include <cstdio>
#include <iostream>
using namespace std;
#define int long long

int exgcd (int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1, y = 0; return a;
    }
    int d = exgcd (b, a % b, x, y);
    int tmp = x;
    x = y; y = tmp - (a / b) * y;
    return d;
}

int calfloor (int a, int b) {
    if (a % b == 0) return a / b;
    if (a > 0) return (a / b);
    return (a / b) - 1;
}

int calceil (int a, int b) {
    if (a % b == 0) return a / b;
    if (a > 0) return (a / b) + 1;
    return (a / b);
}

int solve (int a, int b, int d, int l1, int l2, int r1, int r2) {
    if (d < 0) return 0;
    int x, y; int g = exgcd (a, b, x, y);
    if (d % g != 0) return 0;
    d /= g; a /= g; b /= g;
    x = (x * (d % b) % b + b) % b; y = (d - a * x) / b;
    // x0 = x + kb, y0 = y - ka 就是方程的解。
    // 要满足:l1 <= x + kb <= r1, 即 (l1 - x) / b <= k <= (r1 - x) / b
    // 要满足:l2 <= y - ka <= r2, 即 (y - l2) / a >= k >= (y - r2) / a
    int up = min (calfloor (r1 - x, b), calfloor(y - l2, a)); // 注意是上取整
    int down = max (calceil (l1 - x, b), calceil(y - r2, a)); // 注意是下取整
    if (up < down) return 0;
    return up - down + 1;
}

signed main() {
    int n, a, b, c, x; scanf ("%lld%lld%lld%lld%lld", &n, &a, &b, &c, &x);
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans += solve (b, c, x - a * i, 1, 1, n, n);
    }
    printf ("%lld\n", ans);
    return 0;
}

Ex - Typical Convolution Problem

说实话,这题出得是真的坑。
直接转化式子,$ \frac{f[n]}{a[n]} = \sum{a + b < n} f[a]f[b] = \frac{f[n - 1]}{a[n - 1]} + \sum{a + b = n - 1} f[a]f[b] $
这样分治 fft 即可。

但是,考虑一下细节:

  1. f[0] 的贡献;
  2. a 和 b 的顺序不固定,比如当 a = b 时,估计计算时就只计算了 1 次。
    啊啊啊,细节真多,我也没切,就不放错误代码了。。。

作者 wzy

发表回复