Codeforces Round 946 (Div. 3) 題解
A. Phone Desktop 貪心
優先考慮放 \(2\times2\) 的,然後剩下的補 \(1\times 1\) 的。注意需要上取整即可。
#include<bits/stdc++.h>
using namespace std;
#define ff first
#define ss second
#define pb push_back
#define all(u) u.begin(), u.end()
#define endl '\n'
#define debug(x) cout<<#x<<":"<<x<<endl;
typedef pair<int, int> PII;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10, M = 105;
const int mod = 1e9 + 7;
const int cases = 1;
void Showball() {
int x, y;
cin >> x >> y;
int t = y / 2;
x -= t * 7;
if (y & 1) x -= 11, t++;
cout << t + (max(0, x) + 14) / 15 << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
if (cases) cin >> T;
while (T--)
Showball();
return 0;
}
B. Symmetric Encoding 模擬
解密和加密過程一樣,直接模擬即可。
void Showball() {
string s;
cin >> s >> s;
set<char> st;
for (auto c : s) st.insert(c);
string t = "";
for (auto c : st) t += c;
int n = t.size();
map<char, char> mp;
for (int i = 0; i < n; i++) {
mp[t[i]] = t[n - i - 1];
}
for (auto c : s) cout << mp[c];
cout << endl;
}
C. Beautiful Triple Pairs 計數
題意: 找出所有滿足有一個位置上完全不同,其餘兩個位置相同的三元組對數。
思路: 根據不同元素的位置,可以分成三種情況,為了避免重複,我們可以維護出兩個陣列。
\(mp_{i,j,k}\) 陣列表示三元組 \((i,j,k)\) 的數量。 $s_{i,j} $ 陣列表示三元組 \((x,i,j)\) 的數量。
那麼我們統計第一位不同的情況的答案時,就可以列舉所有的三元組, 每個三元組的貢獻就為
\(mp_{i,j,k}\times (s_{i,j}-mp_{i,j,k})\) 。 表示第一位為 \(i\) 的和第一位不為 \(i\) 的都可以兩兩構成滿足條件的三元組對。
其餘兩種情況同理。這兩個陣列用 \(map\) 維護一下即可。
最後答案記得除以 \(2\) ,因為同一種情況會計算兩遍。
程式碼:
void Showball(){
int n;
cin>>n;
vector<int> a(n);
for(int i=0;i<n;i++) cin>>a[i];
map<array<int,3>,int> mp,mp2,mp3;
map<PII,int> s,s2,s3;
for(int i=0;i<n-2;i++){
mp[{a[i+1],a[i+2],a[i]}]++;
mp2[{a[i],a[i+2],a[i+1]}]++;
mp3[{a[i],a[i+1],a[i+2]}]++;
s[{a[i+1],a[i+2]}]++;
s2[{a[i],a[i+2]}]++;
s3[{a[i],a[i+1]}]++;
}
LL res=0;
for(auto [k,v]:mp) res+=v*(s[{k[0],k[1]}]-v);
for(auto [k,v]:mp2) res+=v*(s2[{k[0],k[1]}]-v);
for(auto [k,v]:mp3) res+=v*(s3[{k[0],k[1]}]-v);
cout<<res/2<<endl;
}
D. Ingenuity-2 思維
題意:給你方向指令,初始兩人在同一點,請你分配兩人去按照指令行走,每人必須至少走一步。使得兩人走完指令後最終到達同一點。
思路:由於初始兩人在同一點,那麼我們想要保證兩人最終在同一點,首先,相反方向可以讓一個人走,就可以相互抵消,對於沒有抵消的部分,如果是偶數次,那麼就可以兩人一起走,反之,則無解。所以直接判斷相反方向的奇偶性是否相同即可。
注意一些特判,n=2時,必須兩條指令相同,因為每人至少走一步。對於 n=4,並且每個方向走出現一次的情況。我們也需要分別讓每個人走一對相反的方向即可。
程式碼:
void Showball() {
int n;
cin >> n;
string s;
cin >> s;
map<char, int> mp;
for (auto c : s) mp[c]++;
if (n == 2 && s[0] != s[1]) return cout << "NO\n", void();
if (n == 4 && mp['N'] && mp['S'] && mp['E'] && mp['W']) {
for (auto c : s) {
if (c == 'N' || c == 'S') cout << "R";
else cout << "H";
}
cout << endl;
return;
}
if ((mp['N'] & 1) != (mp['S'] & 1) || (mp['E'] & 1) != (mp['W'] & 1)) return cout << "NO\n", void();
map<char, int> mp2;
for (auto c : s) {
mp2[c]++;
if (mp2[c] == mp[c]) {
cout << (mp2[c] & 1 ? "R" : "H");
} else {
cout << (mp2[c] & 1 ? "R" : "H");
}
}
cout << endl;
}
E. Money Buys Happiness 思維 DP
題意:給你 \(m\) 個月, 每個月可以賺 \(x\) 元, 每個月你都有一次機會花費 \(c_i\) 元, 獲得 \(h_i\) 的幸福。(當然你目前得有足夠的錢)。 求出能夠獲得的最大幸福值。
注意: \(1 \le m \le 50\) 。
思路:直接貪心顯然是不對的,考慮DP,我們可以求出獲得 \(i\) 幸福值所需的最小花費,然後判斷能否有足夠的錢即可。考慮如何求解, 把花費 \(c_i\) 看成物品價值,把 \(h_i\) 看成物品體積。那麼容易發現,這個問題是一個 \(01\) 揹包問題。那麼狀態轉移就可以得到 :\(f_j = min(f_j, f_{j-h_i}+w_i)\)。
程式碼:
void Showball() {
LL m, x;
cin >> m >> x;
vector<LL> c(m + 1), h(m + 1);
int sum = 0;
for (int i = 1; i <= m; i++) cin >> c[i] >> h[i], sum += h[i];
vector<LL> f(sum + 1, 1e18);
f[0] = 0;
for (int i = 1; i <= m; i++) {
for (int j = sum; j >= h[i]; j--) {
if (f[j - h[i]] + c[i] <= (i - 1)*x)
f[j] = min(f[j], f[j - h[i]] + c[i]);
}
}
for (int i = sum; i >= 0; i--) {
if (f[i] <= (m - 1)*x) {
return cout << i << endl, void();
}
}
}
F. Cutting Game 思維
題意:Alice 和 Bob 玩遊戲,有一個大小為 \(a\times b (1\le a,b \le 10^9)\) 的網格, 網格上有 \(n\) 個籌碼,座標為 \((x_i,y_i)\)。接下來兩人開始輪流操作。一共有四種操作:
\('U'\) \(k\) : 刪除剩餘的前 \(k\) 行。
\('D'\) \(k\) : 刪除剩餘的後 \(k\) 行。
\('L'\) \(k\) : 刪除剩餘的前 \(k\) 列。
\('R'\) \(k\) : 刪除剩餘的後 \(k\) 列。
每次操作後,該選手就會獲得刪除部分存在的所有籌碼數量的得分。
求出操作結束後,兩名選手各自的分數。
思路:因為刪除的行或者列會永遠消失,那麼我們可以維護好剩餘矩形的邊界 \(rl,rr,cl,cr\)。 以第一種操作為例:
注意到矩陣的範圍很大,但是每個點比較離散,那麼可以把所有的點存進一個佇列,並且維護好每一行內部的順序,這樣需要 \(nlogn\) 的時間複雜度。這樣每次統計完就可以把該行從佇列中彈出去即可。由於需要操作頭和尾,所以開雙端佇列即可。對於每一行內部,我們已經排好了序,那麼只需要二分出左右端點的位置,即可算出個數。
小tips:因為我們需要存每一行,每一列都有哪些點。如果開二維陣列的話無疑會爆。所以我們可以開一個 \(map\) 套 \(vector\) 即可。
程式碼:
void Showball() {
int a, b, n, m;
cin >> a >> b >> n >> m;
map<int, vector<int>> row, col;
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
row[x].pb(y);
col[y].pb(x);
}
deque<int> rows, cols;
int rl = 1, rr = a, cl = 1, cr = b;
for (auto &p : row) {
rows.pb(p.ff);
sort(all(p.ss));
}
for (auto &p : col) {
cols.pb(p.ff);
sort(all(p.ss));
}
auto calc = [&](vector<int> vec, int l, int r) {
int ret = 0;
if (l > r) return ret;
auto L = lower_bound(all(vec), l);
auto R = upper_bound(all(vec), r);
ret = R - L;
return ret;
};
int Alice = 0, Bob = 0;
for (int i = 0; i < m; i++) {
char op;
int k;
cin >> op >> k;
int cur = 0;
if (op == 'U') {
while (!rows.empty() && rows.front() < rl + k) {
int x = rows.front(); rows.pop_front();
cur += calc(row[x], cl, cr);
}
rl += k;
} else if (op == 'D') {
while (!rows.empty() && rows.back() > rr - k) {
int x = rows.back(); rows.pop_back();
cur += calc(row[x], cl, cr);
}
rr -= k;
} else if (op == 'L') {
while (!cols.empty() && cols.front() < cl + k) {
int x = cols.front(); cols.pop_front();
cur += calc(col[x], rl, rr);
}
cl += k;
} else {
while (!cols.empty() && cols.back() > cr - k) {
int x = cols.back(); cols.pop_back();
cur += calc(col[x], rl, rr);
}
cr -= k;
}
if (i & 1) Bob += cur;
else Alice += cur;
}
cout << Alice << " " << Bob << endl;
}
G. Money Buys Less Happiness Now 反悔貪心
題意:給你 \(m\) 個月, 每個月可以賺 \(x\) 元, 每個月你都有一次機會花費 \(c_i\) 元, 獲得 \(1\) 單位的幸福。(當然你目前得有足夠的錢)。 求出能夠獲得的最大幸福值。
注意: \(1 \le m \le 2·10^5\) 。
思路:相比 \(E\) 題, \(m\) 的範圍變大了,不能用同樣的方式處理了,但是每次都是獲取 \(1\) 單位的幸福。考慮反悔貪心。記錄當前剩餘的錢為 \(sum\) , 每次 \(sum -=c_i\) ,並且將 \(c_i\) 放入優先佇列中,如果 \(sum < 0\) ,說明需要反悔。自然是選 \(c_i\) 最大的進行反悔。最後佇列元素個數就是答案。每次記得 \(sum += x\)。
程式碼:
void Showball(){
int m, x;
cin >> m >> x;
vector<int> c(m);
for (int i = 0; i < m; i++) {
cin >> c[i];
}
int sum = 0;
priority_queue<int> pq;
for (int i = 0; i < m; i++) {
sum -= c[i];
pq.push(c[i]);
if (sum < 0) sum += pq.top(), pq.pop();
sum += x;
}
cout<< pq.size() << endl;
}