ICPC_2020 上海站


ICPC_2020 上海站

待補完

B

題意

給兩個 \(n\cdot m\) 的字符矩陣 A B 代表一個掃雷的局面,'X' 代表對應位置有雷,'.' 代表對應位置是空的。

要求改變 B 矩陣中的一些字符,使得 A B 兩個矩陣中所有空位上的數字之和相等。

要求改變的字符數量不超過 $\lfloor \frac{nm}{2} \rfloor $ ,如無解輸出 -1 。

\(n,m \leq 1000\)

賽場上

開場就讀了題,一眼不太會,於是就放了。

回頭一看已經過了幾百個隊了。

於是硬着頭皮做,結果還是不太會。鑒於過了這么多人,猜測有結論:肯定是把 B 變成 A 的某種同構(翻折旋轉啥的)。

復制粘貼,寫了快一百行的爛代碼,一發WA

這時候發現把一個矩陣的雷和空格全部轉置,數字之和相等。

大伙試圖找反例——沒找到,直呼不可戰勝

於是把亂搞程序的翻折旋轉加上了這個轉置,順利 AC 。大家紛紛表示這做法絕對是偽的,純亂搞

正經題解

發現把一個矩陣的雷和空格全部轉置,數字之和相等

結論正確性其實不是很難證明。

考慮原矩陣中某個雷周圍有 \(k\) 個空格。

轉置后,該雷變成空格對這 \(k\) 個數字之和的貢獻是 \(-k\)

而這 \(k\) 個空格變成雷,反映在中間這個空格上的值就是 \(k\)

加起來對答案貢獻為 \(0\)

這個證明並不嚴謹,但是總之結論是對的。

那么,若 B 需要變換多於 $\lfloor \frac{nm}{2} \rfloor $ 個字符才能變成 A ,那么考慮把 B 變成轉置后的 A 矩陣,一定只需要變換不超過 $\lfloor \frac{nm}{2} \rfloor $ 個字符就可以了。於是做完了。

#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("\n%s", a[i] + 1);
    for (int i = 1; i <= n; ++i)    scanf("\n%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\n", 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\n", 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("\n%s", a[i] + 1);
    for (int i = 1; i <= n; ++i)    scanf("\n%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_2\leq n\leq 10^9\)

賽場上

顯然就是個純討論推式子的題。

調試半天,一共交了四發WA,最后wjp發現我把一個 \(v_1\) 寫成 \(v_2\) 了。

改掉就 A 了。

正經題解

首先給出一個觀察:兩個人都一刻不停的走動一定可以使答案更優,因此我們假設兩人走路的時間 \(t\) 相同。

為了簡化運算,令 \(p_2=n-p_2\) 表示長度。

首先考慮兩個人都會轉身的情況。顯然地,兩人會在同一位置轉身,否則就會有一些點無法走到了。假設兩人的起點到轉身點的距離分別為 \(x_1,x_2\) ,分別考慮 \(2^2\) 種出發方向,那么我們有

\[\begin{align*} &\begin{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] &\begin{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] &\begin{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] &\begin{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\)時),需要特別判斷

\(t\cdot v_i \leq 2\min(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{2\min(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{2\min(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\n", ans);
    }
    return 0;
}

I

題意

\(n\) 個同心圓,他們的半徑之差是 \(1\) 。作 \(m\) 條過圓心的直線,把每個圓都分成均勻的 \(2m\) 份。

考慮所有直線與圓的交點以及圓心構成的點集。

求點集中任意兩點最短距離的總和。

賽場上

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

然而我沒把三方的調出來,隊友也沒時間寫平方算法了。

總之就是嗚嗚嗚了。

正經題解

首先考慮任意兩個給定點之間的最短距離如何計算。

這里直接給出顯然的結論:

  1. 若兩個點不在同一個圓上,先由外層點經半徑走到內層圓上

  2. 若兩點在同一個圓上:

    從 A 到 B 有三類可能的路徑:

    1. 走圓弧 AB ( \(\LaTeX\) 語法怎么寫不了啊 )
    2. 經過兩條半徑走 \(A\to O \to B\)
    3. 先走到里層的圓上,然后走圓弧,最后走回外層圓

    考慮 \(OA,OB\) 的夾角是 \(\theta\) ,三種方式的路徑長度顯然分別為

    1. \(R\theta\)
    2. \(2R\)
    3. \(2R+(\theta-2)r\)

    其中 \(R,r\) 分別代表外層和里層圓的半徑。那么我們容易得到

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

在這個基礎上,考慮怎么求所有點對的距離和:

\(O(n^2m)\) 算法是顯然的:每一層上的各個點的地位相同,只要計算其中一個,把結果乘上\(2m\) 即可。考慮計算某個點到其他所有點的距離和,我們可以直接枚舉所有的 \(2nm\) 個點依次計算。

考慮層級之間的位似放大關系可以優化到平方復雜度。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM