A. k-Factorization(Codeforces 797A)
思路
本題入手的角度是將輸入的
- 因爲素因子已經是因子分解的最小單位了,所以如果
n 的素因子的數量f 小於k 的話那麼無論如何都無法表示爲k 個數相乘的形式。 - 否則可以將前
k−1 個因子構造成前k−1 個數,將第k 到第f 個因子的乘積構造成第k 個數。並且這樣的解必然存在。
代碼
#include <bits/stdc++.h>
using namespace std;
vector <int> ans;
int n, k, f, tmp;
// 對n進行因式分解
void primeFactor(int n) {
for(int i = 2; i * i <= n; i++) {
while(n % i == 0) {
// 將素因子儲存起來
ans.push_back(i);
n /= i;
}
}
if(n != 1) {
ans.push_back(n);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
primeFactor(n);
f = ans.size();
// 判斷無解的情況
if(f < k) {
cout << -1 << endl;
return 0;
}
// 輸出前k - 1個數
for(int i = 0; i < k - 1; i++) {
cout << ans[i] << ' ';
}
tmp = 1;
for(int i = k - 1; i < f; i++) {
tmp = tmp * ans[i];
}
// 最後一個數由最後幾個素因子合成
if(tmp > 1) {
cout << tmp << endl;
}
return 0;
}
B. Odd sum(Codeforces 797B)
思路
本題的入手點是探究奇數和的構成。
我們知道,奇數和偶數有這樣的性質。
- 奇數 + 奇數 = 偶數
- 奇數 + 偶數 = 奇數
- 偶數 + 偶數 = 偶數
在本題中,子序列裏偶數的數量是不影響子序列和的奇偶性的。爲了讓子序列的和儘可能大,
我們應該將所有非負偶數都包含在子序列中,負偶數可以捨棄
那麼,子序列中的奇數呢?顯然,子序列中的奇數必須有奇數個。(因爲題目保證一定有解所以不用考慮序列中沒有奇數的情況)所以我們可以
我們可以對所有的奇數從大到小排序,先選最大的奇數加入子序列,從而保證子序列的和一定是奇數。然後成對地向子序列中加入奇數,保證子序列和的奇偶性不變。
代碼
#include <bits/stdc++.h>
using namespace std;
vector <int> vec;
int n, m, num, res;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
res = 0;
for(int i = 1; i <= n; i++) {
cin >> num;
// 如果是正奇數或復奇數
if(num % 2 != 0) {
vec.push_back(num);
}
// 如果是正偶數
else if(num > 0) {
res += num;
}
}
m = vec.size();
// 排序
sort(vec.begin(), vec.end());
res += vec[m-1];
for(int i = m - 2; i > 0; i -= 2) {
// 負數就不能被加進答案了
if(vec[i] + vec[i-1] <= 0) {
break;
}
res += (vec[i] + vec[i-1]);
}
cout << res << endl;
return 0;
}
C. Minimal string(Codeforces 797C)
思路
本題的入手點在對
因爲字符串
- 當字符串
s 中還有比當前棧頂字符a 字典序更小的字符b ,那麼我們應該繼續將s 中的字符壓棧(將a 加入u 的末尾比將b 加入更優)。 - 否則將當前棧頂字符
a 加入字符串u 的末尾,並讓棧彈出棧頂元素。
代碼
#include <bits/stdc++.h>
using namespace std;
string str;
stack <char> sta;
queue <char> q;
int n, cur, cnt[300];
// 判斷s中是否還有比ch字典序小的字符
bool isGod(char ch) {
for(int i = 'a'; i < ch; i++) {
if(cnt[i] > 0) {
return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> str;
n = str.size();
for(int i = 0; i < n; i++) {
// 統計字符出現次數,供isGod函數使用
cnt[str[i]]++;
}
cur = 0;
while(cur < n) {
// 若棧是空棧則壓棧
if(sta.empty()) {
sta.push(str[cur]);
cnt[str[cur]]--;
cur++;
}
// 若當前棧頂字符已是字典序最小
else if(isGod(sta.top())) {
q.push(sta.top());
sta.pop();
}
// 否則
else {
sta.push(str[cur]);
cnt[str[cur]]--;
cur++;
}
}
// 棧內可能還有殘留字符
while(!sta.empty()) {
q.push(sta.top());
sta.pop();
}
while(!q.empty()) {
cout << q.front();
q.pop();
}
return 0;
}
D. Broken BST(Codeforces 797D)
思路
本題的着手點在對
在查找過程中,每到一個節點,
比方說根節點
於是我們可以用
代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10, maxv = 1e9;
map <int, int> cnt;
bool hf[maxn];
int v[maxn], l[maxn], r[maxn];
int n, root, ans;
// DFS序遍歷樹的同時維護區間[a, b]
void dfs(int u, int a, int b) {
if(a > b) {
return;
}
if(a <= v[u] && v[u] <= b) {
ans += cnt[v[u]];
}
if(l[u] >= 0) {
dfs(l[u], a, min(b, v[u] - 1));
}
if(r[u] >= 0) {
dfs(r[u], max(a, v[u] + 1), b);
}
}
int main() {
// freopen("Input2.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> v[i] >> l[i] >> r[i];
if(l[i] >= 0) {
hf[l[i]] = true;
}
if(r[i] >= 0) {
hf[r[i]] = true;
}
cnt[v[i]]++;
}
// 尋找根節點
for(int i = 1; i <= n; i++) {
if(hf[i] == false) {
root = i;
}
}
dfs(root, 0, maxv);
cout << n - ans << endl;
return 0;
}
E. Array Queries(Codeforces 797E)
思路
本題的入手點在於分別考慮解決問題的難點,以及巧妙地暴力。
- 在不考慮
n 的數據規模的情況下,可以根據每個查詢暴力解出答案(用變量的變化模擬p 的移動)。 - 在不考慮
k 的數據規模的情況下,可以進行動態規劃。令d[i][j] 表示當前起始位置爲i ,k 爲j 的情況下需要多少步能夠走出數組,那麼按照i 從大到小的順序DP 就能得到答案d[p][k] 。
但是本題的問題在於
代碼
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 10;
int q, p, k, n, a[maxn], d[maxn][110];
// 暴力計算答案
int brute(int p, int k) {
int res = 0;
while(p <= n) {
p += (a[p] + k);
res++;
}
return res;
}
int main() {
// freopen("Input1.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
}
// 動態規劃
for(int i = n; i >= 1; i--) {
for(int j = 1; j <= 100; j++) {
if(i + a[i] + j > n) {
d[i][j] = 1;
}
else {
d[i][j] = d[i+a[i]+j][j] + 1;
}
}
}
cin >> q;
while(q--) {
cin >> p >> k;
if(k <= 100) {
cout << d[p][k] << endl;
}
else {
cout << brute(p, k) << endl;
}
}
return 0;
}
F. Mice and Holes(Codeforces 797F)
思路
本題的入手點在於恰當地選擇解決問題的順序,能設計動態規劃算法並使用數據結構進行優化。
在不考慮任何數據規模的情況下,我們顯然可以對每隻老鼠考慮它們在哪個洞中,或對每個洞考慮它收容哪些老鼠。如果用那麼可以用搜索將問題順利解決。
但是顯然我們不能不考慮數據規模,在本題的數據規模下,我們或許能夠利用以上兩種順序中的一個,通過動態規劃加以解決。爲了讓問題更清晰,我們似乎應該對老鼠和老鼠洞按座標排序。
考慮讓
在思考實現方式的過程中要注意以下幾個問題:
- 如何用數據結構優化
RMQ (單調隊列)? - 如何快速得知前
j 個老鼠到第i 個洞的距離之和(預處理)? - 如何優化算法的空間複雜度(滾動數組)?
- 累加數據會不會出問題(防溢出)?
代碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair <ll, int> p;
const int maxn = 5e3 + 5;
const ll INF = 1e13;
// 用結構體表示的單調隊列
struct queue {
int l, r;
int idx[maxn];
ll val[maxn];
void init() {
l = 0;
r = -1;
}
// 單調隊列彈出首元素
void pop(int k) {
while(r >= l && idx[l] <= k) {
l++;
}
}
// 單調隊列插入尾元素
void insert(ll v, int i) {
while(r >= l && val[r] >= v) {
r--;
}
val[++r] = v;
idx[r] = i;
}
ll getMin() {
return val[l];
}
}q;
int n, m, last, cur, mouse[maxn];
ll sum, pre[maxn], d[2][maxn];
p hole[maxn];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) {
cin >> mouse[i];
}
for(int i = 1; i <= m; i++) {
cin >> hole[i].first >> hole[i].second;
sum += hole[i].second;
}
// 若老鼠洞的總容量不夠則無解
if(sum < n) {
cout << -1 << endl;
return 0;
}
// 對老鼠和老鼠洞按照座標從小到大排序
sort(mouse + 1, mouse + n + 1);
sort(hole + 1, hole + m + 1);
// DP的初始化
fill(d[0] + 1, d[0] + n + 1, INF);
cur = 1;
for(int i = 1; i <= m; i++) {
int x = hole[i].first;
int w = hole[i].second;
// 預處理距離和
for(int j = 1; j <= n; j++) {
pre[j] = pre[j-1] + abs((mouse[j] - x));
}
// 初始化單調隊列
q.init();
q.insert(0, 0);
// 計算d[i][j]
for(int j = 1; j <= n; j++) {
q.insert(d[last][j] - pre[j], j);
d[cur][j] = q.getMin() + pre[j];
q.pop(j - w);
}
// 滾動數組
last = cur;
cur ^= 1;
}
cout << d[last][n] << endl;
return 0;
}