Codeforces Round 946 (Div. 3) 題解

Showball發表於2024-05-28

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;
}

相關文章