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\) 種出發方向,那么我們有
分別解這幾個方程就可以得到 \(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\)
如該式不成立需要舍去。
考慮只有一個人轉身的情況:
還有一種對稱的就是:
我就是把這種的 \(v_2\) 寫成 \(v_1\) 了(
考慮不轉身的情況。
最后把所有的 \(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)\) 的。
然而我沒把三方的調出來,隊友也沒時間寫平方算法了。
總之就是嗚嗚嗚了。
正經題解
首先考慮任意兩個給定點之間的最短距離如何計算。
這里直接給出顯然的結論:
-
若兩個點不在同一個圓上,先由外層點經半徑走到內層圓上
-
若兩點在同一個圓上:
從 A 到 B 有三類可能的路徑:
- 走圓弧 AB ( \(\LaTeX\) 語法怎么寫不了啊 )
- 經過兩條半徑走 \(A\to O \to B\)
- 先走到里層的圓上,然后走圓弧,最后走回外層圓
考慮 \(OA,OB\) 的夾角是 \(\theta\) ,三種方式的路徑長度顯然分別為
- \(R\theta\)
- \(2R\)
- \(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\) 個點依次計算。
考慮層級之間的位似放大關系可以優化到平方復雜度。