ICPC_2020 上海站

ICPC_2020 上海站

待补完

B

题意

给两个 (ncdot m) 的字符矩阵 A B 代表一个扫雷的局面,'X' 代表对应位置有雷,'.' 代表对应位置是空的。

要求改变 B 矩阵中的一些字符,使得 A B 两个矩阵中所有空位上的数字之和相等。

要求改变的字符数量不超过 $lfloor frac{nm}{2} floor $ ,如无解输出 -1 。

(n,m leq 1000)

赛场上

开场就读了题,一眼不太会,于是就放了。

回头一看已经过了几百个队了。

于是硬着头皮做,结果还是不太会。鉴于过了这么多人,猜测有结论:肯定是把 B 变成 A 的某种同构(翻折旋转啥的)。

复制粘贴,写了快一百行的烂代码,一发WA

这时候发现把一个矩阵的雷和空格全部转置,数字之和相等。

大伙试图找反例——没找到,直呼不可战胜

于是把乱搞程序的翻折旋转加上了这个转置,顺利 AC 。大家纷纷表示这做法绝对是伪的,纯乱搞

正经题解

发现把一个矩阵的雷和空格全部转置,数字之和相等

结论正确性其实不是很难证明。

考虑原矩阵中某个雷周围有 (k) 个空格。

转置后,该雷变成空格对这 (k) 个数字之和的贡献是 (-k)

而这 (k) 个空格变成雷,反映在中间这个空格上的值就是 (k)

加起来对答案贡献为 (0)

这个证明并不严谨,但是总之结论是对的。

那么,若 B 需要变换多于 $lfloor frac{nm}{2} floor $ 个字符才能变成 A ,那么考虑把 B 变成转置后的 A 矩阵,一定只需要变换不超过 $lfloor frac{nm}{2} floor $ 个字符就可以了。于是做完了。

#include <bits/stdc++.h>
using namespace std;

const int N = 1024;
char a[N][N], b[N][N];
int n, m;

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)    scanf("
%s", a[i] + 1);
    for (int i = 1; i <= n; ++i)    scanf("
%s", b[i] + 1);

    int cnt = 0;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j )
            cnt += (a[i][j] != b[i][j]);

    if (cnt > n * m / 2) {
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                a[i][j] = (a[i][j] == 'X')? '.': 'X';
    }

    for (int i = 1; i <= n; ++i)
        printf("%s
", a[i] + 1);

    return 0;
}

顺带一提赛场上是这样写的:

#include <bits/stdc++.h>
#define REP for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j)
using namespace std;

const int N = 1024;
int n, m, lim;
char a[N][N], b[N][N], c[N][N];

void output() {
    for (int i = 1; i <= n; ++i) {
        printf("%s
", a[i] + 1);
    }
    exit(0);
}

void copy() {
    REP a[i][j] = c[i][j];
}

void turn() {
    if (n != m) return ;
    REP c[j][n - i + 1] = a[i][j];
    copy();
}

int comp() {
    int ans = 0;
    REP ans += (a[i][j] != b[i][j]);
    return ans;
}

void ccomp() {
    if (comp() <= lim) output();	turn();
    if (comp() <= lim) output();	turn();
    if (comp() <= lim) output();	turn();
    if (comp() <= lim) output();	turn();
}

void flip_x() {
    REP c[i][j] = a[i][m - j + 1];
    copy();
}

void flip_y() {
    REP c[i][j] = a[n - i + 1][j];
    copy();
}

void flip_tt() {
    if (n != m) return ;
    REP c[i][j] = a[j][i];
    copy();
}

void rev() {
    REP a[i][j] = (a[i][j] == '.') ? ('X') : ('.');
}

void cccomp() {
    ccomp();	rev();
    ccomp();	rev();
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i)    scanf("
%s", a[i] + 1);
    for (int i = 1; i <= n; ++i)    scanf("
%s", b[i] + 1);
    lim = n * m / 2;
    cccomp();
    flip_x();						cccomp();
    flip_x(), flip_y();				cccomp();
    flip_x();						cccomp();
    flip_x(), flip_y(), flip_tt();	cccomp();
    flip_x();						cccomp();
    flip_x(), flip_y();				cccomp();
    flip_x();						cccomp();
    puts("-1");
    return 0;
}

C

待补完

D

第四发终于A掉这题的znr:「我直接切腹得了」

题意

考虑一个数轴。在 (p_1,p_2) 两个位置站了两个人,他们走路的速度分别为 (v_1,v_2)

现在要求两个人走完 ([0,n]) 之间的所有点,求最短时间。

(p_1,p_2leq nleq 10^9)

赛场上

显然就是个纯讨论推式子的题。

调试半天,一共交了四发WA,最后wjp发现我把一个 (v_1) 写成 (v_2) 了。

改掉就 A 了。

正经题解

首先给出一个观察:两个人都一刻不停的走动一定可以使答案更优,因此我们假设两人走路的时间 (t) 相同。

为了简化运算,令 (p_2=n-p_2) 表示长度。

首先考虑两个人都会转身的情况。显然地,两人会在同一位置转身,否则就会有一些点无法走到了。假设两人的起点到转身点的距离分别为 (x_1,x_2) ,分别考虑 (2^2) 种出发方向,那么我们有

[egin{align*} &egin{cases} large frac{p_1+2x_1}{v_1}=frac{p_2+2x_2}{v_2}=t \ x_1+x_2=n-p_1-p_2 end{cases} \[0.5cm] &egin{cases} large frac{p_1+2x_1}{v_1}=frac{2p_2+x_2}{v_2}=t \ x_1+x_2=n-p_1-p_2 end{cases} \[0.5cm] &egin{cases} large frac{2p_1+x_1}{v_1}=frac{p_2+2x_2}{v_2}=t \ x_1+x_2=n-p_1-p_2 end{cases} \[0.5cm] &egin{cases} large frac{2p_1+x_1}{v_1}=frac{2p_2+x_2}{v_2}=t \ x_1+x_2=n-p_1-p_2 end{cases} end{align*} ]

分别解这几个方程就可以得到 (t)(p_1,p_2,v_1,v_2) 的关系。

值得注意的是,解得的结果 (t) 很可能是非法的(如(p_1=p_2)时),需要特别判断

(tcdot v_i leq 2min(p_i,n-p_1-p_2)+max(p_i,n-p_1-p_2),~~i=1,2)

如该式不成立需要舍去。

考虑只有一个人转身的情况:

[t=max(frac{p_1}{v_1},frac{2min(p_2,n-p_1-p_2)+max(p_2,n-p_1-p_2)}{v_2}) ]

还有一种对称的就是:

[t=max(frac{p_2}{v_2},frac{2min(p_1,n-p_1-p_2)+max(p_1,n-p_1-p_2)}{v_1}) ]

我就是把这种的 (v_2) 写成 (v_1) 了(

考虑不转身的情况。

[t=max(frac{n-p_1}{v_1},frac{n-p_2}{v_2}) ]

最后把所有的 (t) 放到一起求个 (min) 就得了。

附赛场AC代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    long double p1, p2, v1, v2, n;
    int T;  cin >> T;
    while (T--) {
        cin >> n >> p1 >> v1 >> p2 >> v2;
        if (p1 > p2) {
            swap(p1, p2);
            swap(v1, v2);
        }
        p2 = n - p2;
        long double t1 = (n + p1 + p2) / (v1 + v2),
                    t2 = (2 * n - p1 - p2) / (v1 + v2),
                    t3 = (2 * n + 2 * p1 - p2) / (2 * v1 + v2),
                    t4 = (2 * n + 2 * p2 - p1) / (2 * v2 + v1),
                    t5 = (min(p1, n - p1) * 2 + max(p1, n - p1)) / (v1),
                    t6 = (min(p2, n - p2) * 2 + max(p2, n - p2)) / (v2),
                    t7 = max((p1 / v1), (min(p2, n - p1 - p2) * 2 + max(p2, n - p1 - p2)) / v2),
                    t8 = max((p2 / v2), (min(p1, n - p1 - p2) * 2 + max(p1, n - p1 - p2)) / v1);
        long double t9 = max((n - p1) / v1, (n - p2) / v2);
        long double d = (n - p1 - p2);
        if (t2 * v1 > 2 * min(d, p1) + max(d, p1))  t2 = 1e20;
        if (t2 * v2 > 2 * min(d, p2) + max(d, p2))  t2 = 1e20;
        if (t3 * v1 > 2 * min(d, p1) + max(d, p1))  t3 = 1e20;
        if (t3 * v2 > 2 * min(d, p2) + max(d, p2))  t3 = 1e20;
        if (t4 * v1 > 2 * min(d, p1) + max(d, p1))  t4 = 1e20;
        if (t4 * v2 > 2 * min(d, p2) + max(d, p2))  t4 = 1e20;
        if (t1 * v1 > 2 * min(d, p1) + max(d, p1))  t1 = 1e20;
        if (t1 * v2 > 2 * min(d, p2) + max(d, p2))  t1 = 1e20;
        long double ans = min(min(min(t1, t2), min(t3, t4)), min(min(t5, t6), min(t7, t8)));
        ans = min(ans, t9);
        printf("%.10Lf
", ans);
    }
    return 0;
}

I

题意

(n) 个同心圆,他们的半径之差是 (1) 。作 (m) 条过圆心的直线,把每个圆都分成均匀的 (2m) 份。

考虑所有直线与圆的交点以及圆心构成的点集。

求点集中任意两点最短距离的总和。

赛场上

不仅想出了 (O(n^3)) 解法,甚至想到了 (O(n^2)) 的。

然而我没把三方的调出来,队友也没时间写平方算法了。

总之就是呜呜呜了。

正经题解

首先考虑任意两个给定点之间的最短距离如何计算。

这里直接给出显然的结论:

  1. 若两个点不在同一个圆上,先由外层点经半径走到内层圆上

  2. 若两点在同一个圆上:

    从 A 到 B 有三类可能的路径:

    1. 走圆弧 AB ( (LaTeX) 语法怎么写不了啊 )
    2. 经过两条半径走 (A o O o B)
    3. 先走到里层的圆上,然后走圆弧,最后走回外层圆

    考虑 (OA,OB) 的夹角是 ( heta) ,三种方式的路径长度显然分别为

    1. (R heta)
    2. (2R)
    3. (2R+( heta-2)r)

    其中 (R,r) 分别代表外层和里层圆的半径。那么我们容易得到

    [min(R heta,2R,2R+( heta-2)r) = egin{cases} R heta & heta leq 2\ 2R & heta > 2 end{cases} ]

在这个基础上,考虑怎么求所有点对的距离和:

(O(n^2m)) 算法是显然的:每一层上的各个点的地位相同,只要计算其中一个,把结果乘上(2m) 即可。考虑计算某个点到其他所有点的距离和,我们可以直接枚举所有的 (2nm) 个点依次计算。

考虑层级之间的位似放大关系可以优化到平方复杂度。

原文地址:https://www.cnblogs.com/Shimarin/p/14131076.html