CF Round 775 Div2 題解
A題 Game
給定一個長度為 \(n\) 的一維線性地圖,位置 \(i\) 處可能是陸地或者水。
現在我們要從位置 \(1\) 到達位置 \(n\) 處(保證這兩個地方都是水),我們有兩種方式:
- 如果位置 \(i\) 和位置 \(i+1\) 都是陸地,那麼可以不耗費代價來互相移動
- 可以從位置 \(x\) 跳到位置 \(y\),消耗代價為 \(|y-x|\) (該操作至多進行一次)
問需要花費至少多少代價來從位置 \(1\) 到達位置 \(n\)。
\(1\leq n \leq 100\)
從頭尾分別延伸最長的連續陸地串,然後中間直接跳即可。
#include<bits/stdc++.h> using namespace std; const int N = 110; int n, a[N]; int solve() { cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; // bool flag = true; for (int i = 1; i <= n; ++i) if (a[i] == 0) flag = false; if (flag) return 0; int l, r; for (int i = 1; i <= n; ++i) if (a[i] == 0) { l = i - 1; break; } for (int i = n; i >= 1; i--) if (a[i] == 0) { r = i + 1; break; } return r - l; } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }
B題 Game of Ball Passing (數學)
建議直接看原題面
我們對整個陣列從小到大排個序,記整個陣列前 \(n-1\) 個元素之和為 \(L\),最大元素的值為 \(R\)。
當 \(L+1\geq R\) 的時候,不難構造出只含有一個球的方案。
當 \(L+1<R\) 時,我們發現,每次一個球都會使得 \(R\) 和 \(L\) 的差值減小 1,所以答案為 \(R-L\) 。
#include<bits/stdc++.h> using namespace std; #define LL long long const int N = 100010; int n; LL a[N]; int solve() { cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; sort(a + 1, a + n + 1); if (a[n] == 0) return 0; LL L = 0, R = a[n]; for (int i = 1; i < n; ++i) L += a[i]; return max(R - L, 1LL); } int main() { int T; cin >> T; while (T--) cout << solve() << endl; return 0; }
C題 Weird Sum(字首和)
給定一張 \(n\) 行 \(m\) 列的地圖,每個格子都有一個自己的顏色 \(c_{i,j}\)。
我們記同一顏色 \(x\) 的格子的集合為 \(S_x\),那麼 \(f(S_x)\) 為集合內格子兩兩曼哈頓距離之和,試求出 \(\sum\limits_{x\in M} f(S_x)\) 的值。(\(M\) 為地圖中所有顏色之和)
\(1\leq n \leq m,nm\leq 10^6,1\leq c_{i,j}\leq 10^6\)
這個顯然,我們開 \(10^6\) 個 vector,講相同顏色的格子放入對應顏色的 vector 裡面,然後直接對每個 vector 求解即可。
不過,我們平方列舉肯定不行,得優化到線性複雜度。
我們考慮座標僅有一維的情況,那麼我們直接將座標排個序,然後做一次字首和,按照算貢獻的方式來統計答案,就能夠將平方列舉優化到線性。
\[\sum\limits_{i=1}^{n-1}\sum\limits_{j=1+1}^n |x_j-x_i|=\sum\limits_{i=1}^{n-1}(\sum\limits_{j=i+1}^n x_j-(n-i)x_i) \]對於二維的曼哈頓距離,由於兩軸座標互相獨立,互不影響,所以我們直接分別排序統計後相加即可。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int C = 100010;
int n, m;
vector<pair<int, int> > color[C];
LL x[C], y[C], xx[C], yy[C];
LL solve(int c) {
vector<pair<int, int> > &arr = color[c];
int cnt = 0;
for (auto it : arr) x[++cnt] = it.first, y[cnt] = it.second;
sort(x + 1, x + cnt + 1);
sort(y + 1, y + cnt + 1);
for (int i = 1; i <= cnt; ++i)
xx[i] = xx[i - 1] + x[i], yy[i] = yy[i - 1] + y[i];
LL res = 0;
for (int i = 1; i < cnt; ++i) {
int len = cnt - i;
res += (xx[cnt] - xx[i]) - x[i] * len;
res += (yy[cnt] - yy[i]) - y[i] * len;
}
return res;
}
int main()
{
//read
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1, c; j <= m; ++j) {
cin >> c;
color[c].push_back(make_pair(i, j));
}
//solve
LL ans = 0;
for (int i = 1; i < C; ++i) ans += solve(i);
cout << ans << endl;
return 0;
}
D題 Integral Array(倍數列舉)
給定一個數組 \(\{a_n\}\),問是否對於內部任意兩個元素 \(x,y(x\geq y)\),\(\lfloor\frac{x}{y}\rfloor\) 也在陣列內?
\(1\leq n\leq 10^6,1\leq a_i\leq c \leq 10^6\)
一個偏暴力的 TLE 寫法
我們將陣列排個序並 unique 一下,然後從小到大依次將數放進去,每次加入一個新數字的時候,只列舉 \(\sqrt{a_i}\) 範圍內的數,如果這個數存在,就看看整除的結果在不在陣列內;如果不在,則看範圍 \([L,R]\) 之內的數是否存在(\(L,R\) 滿足 \(a_i\) 除以區間內的數得到的結果都等於這個數,可以 \(O(1)\) 的計算 \(L,R\))。考慮到單點修改和區間查詢,所以我們維護一個 set 或者樹狀陣列/線段樹。
總複雜度 \(O(n\sqrt c\log c)\),對於 \(10^5\) 的資料還能掙扎一下,但是 \(10^6\) 屬實有點為難了。
正解:倍數列舉
我們一開始就建立好 vis 陣列並做一次字首和(我們上面那個方法是動態插入的,所以有一個 \(O(\log n)\) 的複雜度,這裡直接是一開始全部處理好,然後實現 \(O(1)\) 查詢)。
我們列舉每一種值,倘若這個值存在,我們記為 \(x\),那麼我們直接列舉 \(\lfloor\frac{y}{x}\rfloor\) 的值,記為 \(k\),那麼有 \(kx\leq y <k(x+1)\)。我們看看區間 \([kx,kx+x-1]\) 內是否存在數,如果存在,那麼我們就要查詢一下 \(k\) 是否存在了。
這個的複雜度就是純 \(O(n+c\sqrt{c})\),其實也就是少了一個 \(O(\log c)\) 的複雜度。
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, c, a[N];
//
int vis[N], pre[N];
inline bool query(int L, int R) { return pre[R] > pre[L - 1]; }
//
bool solve()
{
//read
scanf("%d%d", &n, &c);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
//init
memset(vis, 0, sizeof(int) * (c + 1));
for (int i = 1; i <= n; ++i)
vis[a[i]] = 1;
for (int i = 1; i <= c; ++i)
pre[i] = pre[i - 1] + vis[i];
//solve
for (int x = 1; x <= c; ++x) {
if (!vis[x]) continue;
for (int k = 1; k * x <= c; ++k)
if (query(k * x, min(c, k * x + x - 1)) && !vis[k])
return false;
}
return true;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) puts(solve() ? "Yes" : "No");
return 0;
}