Codeforces Round 965 (Div. 2)

Luckyblock發表於2024-08-11

目錄
  • 寫在前面
  • A
  • B
  • C
  • D
  • E1
  • 寫在最後

寫在前面

比賽地址:https://codeforces.com/contest/1993

為了保證隊長當前是 1k9 這個事實不變方便勸誘新大神,於是上小號。

比較手速場呃呃,小號大概也能上紫了爽,要是手快點還能更爽。

置頂廣告:中南大學 ACM 集訓隊絕贊招新中!

有資訊奧賽基礎,獲得 NOIP 省一等獎並達到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我與校隊隊長聯絡,免選拔直接進校集訓隊參加區域賽!

沒有達到該水平但有志於 XPCX 賽事請關注每學年開始的 ACM 校隊招新喵!

到這個時候了還缺隊友實在不妙!求求求求快來個大神帶我嗚嗚嗚嗚

A

簽到。

草怎麼那麼多 fst 的不懂。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    int xc, yc, k; std::cin >> xc >> yc >> k;
    for (int i = 1; i <= k - k % 2; i += 2) {
      std::cout << xc + (i / 2 + 1) << " " << yc << "\n";
      std::cout << xc - (i / 2 + 1) << " " << yc << "\n";
    }
    if (k % 2) std::cout << xc << " " << yc << "\n";
  }
  return 0;
}

B

思維,結論。

本地 check 了一下發現樣例中給定排列與答案裡,有且僅有 \([1, n]\) 的和是相等的,於是猜想一定存在一種構造方案,使得兩個排列僅有 \([1, n]\) 的和相等。

賽時猜了個結論,直接把所有位置迴圈向後位移一位即可,本地跑了幾組資料 check 了一下發現可行於是交上去過了。

其正確性是顯然的,考慮給定的排列 \(a\) 與迴圈向後位移一位得到的排列 \(b\),對於任意一個長度小於 \(n\) 的區間 \([l, r]\),一定有:

\[\sum_{l\le i\le r} b_i = \sum_{l - 1\le i\le r - 1} a_i \not= \sum_{l\le i\le r} a_i \]

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================4
int n, a[kN], ans[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    for (int i = 2; i <= n; ++ i) ans[i] = a[i - 1];
    ans[1] = a[n];
    for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";

    // LL cnt = 0;
    // for (int i = 1; i <= n; ++ i) {
    //   for (int j = i; j <= n; ++ j) {
    //     LL s1 = 0, s2 = 0;
    //     for (int k = i; k <= j; ++ k) {
    //       s1 += a[k], s2 += ans[k];
    //     }
    //     if (s1 == s2) ++ cnt;
    //   }
    // }
    // std::cout << cnt << "\n";
    std::cout << "\n";
  } 
  return 0;
}
/*
3
2
1 2
2 1
5
1 2 3 4 5
3 5 4 2 1
7
4 7 5 1 2 6 3
6 2 1 4 7 3 5
*/

C

列舉,資料結構。

考慮列舉最終答案裡的 \(a_i\),再考慮透過修改增大 \(a_i\) 和增大中位數的代價:

若有 \(b_i = 1\),顯然一次修改至少使 \(a_i:=a_i + 1\) 而不一定使中位數 \(+1\),顯然此時應僅修改 \(a_i\),對答案的貢獻即 \(a_i + k + \operatorname{median}(c_i)\),直接對頂堆動態維護刪去 \(a_i\) 的中位數即可。單次檢查的時間複雜度為 \(O(\log n)\) 級別。

對頂堆維護中位數可見:https://www.cnblogs.com/luckyblock/p/18159496

若有 \(b_i = 0\),則此時僅能修改其他位置使中位數增大,一個顯然的想法是二分答案列舉增大後的中位數的值 \(\operatorname{mid}\),則僅需檢查能否透過修改使 \(c = \left\lfloor\frac{n - 1}{2}\right\rfloor + 1\) 個數不小於 \(\operatorname{mid}\)

考慮先查詢原數列中不小於 \(\operatorname{mid}\) 的數的個數 \(c'\),再查詢原數列中小於 \(\operatorname{mid}\)\(b_i=1\) 的數,則最優的操作是選擇其中最大的 \(c - c'\) 個數使它們變為 \(\operatorname{mid}\),查詢他們的和與 \((c - c')\times \operatorname{mid}\) 的差值是否不大於 \(k\) 即可。

上述原數列中不小於 \(\operatorname{mid}\) 的數的個數、以及原數列中小於 \(\operatorname{mid}\)\(b_i=1\) 的數,均可以透過維護排序後的原數列,再在上面二分得到,在此基礎上最大的 \(c - c'\) 個數可透過維護字首和得到,則單次檢查的時間複雜度為 \(O(\log v\log n)\) 級別。

總時間複雜度 \(O(n\log v\log n)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 2e5 + 10;
const LL kMaxa = 2e9;
//=============================================================
int n, k, a[kN], b[kN];
int a1num, sorta1[kN], sorta[kN];
LL sum[kN];
//=============================================================
namespace Set {
  const int kInf = 1e9 + 2077;
  std::multiset<int> less, greater;
  void init() {
    less.clear(), greater.clear();
    less.insert(-kInf), greater.insert(kInf);
  }
  void adjust() {
    while (less.size() > greater.size() + 1) {
      std::multiset<int>::iterator it = (--less.end());
      greater.insert(*it);
      less.erase(it);
    }
    while (greater.size() > less.size()) {
      std::multiset<int>::iterator it = greater.begin();
      less.insert(*it);
      greater.erase(it);
    }
  }
  void add(int val_) {
    if (val_ <= *greater.begin()) less.insert(val_);
    else greater.insert(val_);
    adjust();
  }
  void del(int val_) {
    std::multiset<int>::iterator it = less.lower_bound(val_);
    if (it != less.end()) {
      less.erase(it);
    } else {
      it = greater.lower_bound(val_);
      greater.erase(it);
    }
    adjust();
  }
  int get_middle() {
    return *less.rbegin();
  }
}
void init() {
  std::cin >> n >> k;
  Set::init();
  for (int i = 1; i <= n; ++ i) {
    std::cin >> a[i];
    Set::add(a[i]);
  }

  a1num = 0;
  for (int i = 1; i <= n; ++ i) {
    std::cin >> b[i];
    sorta[i] = a[i];
    if (b[i] == 1) sorta1[++ a1num] = a[i];
  }
  std::sort(sorta + 1, sorta + n + 1);
  std::sort(sorta1 + 1, sorta1 + a1num + 1);
  for (int i = 1; i <= a1num; ++ i) sum[i] = sorta1[i] + sum[i - 1];
}
LL solveb1(int pos_) {
  Set::del(a[pos_]);
  LL ret = Set::get_middle();
  Set::add(a[pos_]);
  return ret;
}
LL solveb0(int pos_) {
  LL ret = 0;
  int middle = (n - 1) / 2 + 1;

  for (LL l = 1, r = kMaxa; l <= r; ) {
    LL mid = (l + r) >> 1ll;
    int p1 = std::lower_bound(sorta1 + 1, sorta1 + a1num + 1, mid) - sorta1;
    int p2 = std::lower_bound(sorta + 1, sorta + n + 1, mid) - sorta;
    int c1 = p1 - 1, c2 = n - (p2 - 1) - (a[pos_] >= mid);

    if (c2 >= middle) {
      ret = mid;
      l = mid + 1;
      continue;
    }

    int need = middle - c2;
    if (need > c1) {
      r = mid - 1;
      continue;
    }

    if (1ll * sum[c1] - sum[c1 - need] + k < 1ll * need * mid) {
      r = mid - 1;
    } else {
      ret = mid;
      l = mid + 1;
    }
  }
  return ret;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    init();
    LL ans = 0;
    for (int i = 1; i <= n; ++ i) {
      if (b[i] == 1) {
        ans = std::max(ans, 1ll * a[i] + k + solveb1(i));
      } else {
        ans = std::max(ans, 1ll * a[i] + solveb0(i));
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

D

DP。

遊戲題照例先先手玩下。以下稱先手移動的僅能走邊 \((i, i + 1)\) 的牛牛為先手,另一牛牛為後手。邊 \((i, i + 1)\) 稱為雜魚邊,其他邊稱為牛逼邊。

發現考慮先手必勝需要大力列舉起點比較麻煩,於是取個反考慮起點固定為 1 的後手必勝。

發現對於某個先手的起點 \(s\),後手必勝的充要條件是存在一條牛逼邊 \((u, v)\),滿足:

  • \(s > u\),使得先手不會把島嶼 \(u\) 幹掉。
  • \(f_u\) 為後手從 1 到 \(u\) 的最短路的長度,有 \(s < v - (f_{u} + 1)\),使得後手可以比先手更早地到達 \(v\),將先手到達終點的必經之路幹掉。

於是考慮拓撲排序 DP 求得從 1 到每個節點的最短路徑,在此過程中對於每個節點 \(u\) 列舉所有出邊 \((u, v)\),則對於先手的起點 \(s\in [u + 1, v - (f_{u} + 1) - 1]\) 均為後手必勝的,可透過差分維護。

最後還原答案序列輸出即可,總時間複雜度 \(O(n + m)\) 級別。

雖然賽時懶得想了真的寫了拓撲排序但實際上並無必要,拓撲序實際上即 \(1\sim n\),直接大力列舉即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m;
int edgenum, into[kN], head[kN], v[kN << 1], ne[kN << 1];
int ans[kN], f[kN];
//=============================================================
void addedge(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;

  ++ into[v_];
}
void topsort() {
  std::queue<int> q;
  for (int i = 1; i <= n; ++ i) f[i] = kN;
  f[1] = 0;
  q.push(1);

  while (!q.empty()) {
    int u_ = q.front(); q.pop();
    for (int i = head[u_]; i; i = ne[i]) {
      int v_ = v[i];
      f[v_] = std::min(f[v_], f[u_] + 1);
      int l = u_ + 1, r = v_ - (f[u_] + 1) - 1;
      if (l <= r) ++ ans[l], -- ans[r + 1];

      if (!(-- into[v_])) q.push(v_);
    }
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> m;
    edgenum = 0;
    for (int i = 1; i <= n; ++ i) head[i] = into[i] = ans[i] = 0;
    for (int i = 1; i < n; ++ i) addedge(i, i + 1);
    for (int i = 1; i <= m; ++ i) {
      int u_, v_; std::cin >> u_ >> v_;
      addedge(u_, v_);
    }
    topsort();
    for (int i = 1; i < n; ++ i) {
      ans[i] += ans[i - 1];
      std::cout << (ans[i] <= 0);
    }
    std::cout << "\n";
  }
  return 0;
}
/*
1
15 3
2 8
4 9
8 15

11000111000111
12345678901234
*/

E1

媽的什麼東西怎麼開局十分鐘就有光速過的

寫在最後

學到了什麼:

  • C:想好在寫!別看到什麼求什麼數量啊 k 大值啊就高潮了光速拉個板子過了改了改發現還歹刪了唐氏得一批

你說的對按照常理來說現在又是夾帶私貨環節,為師已經迫不及待地要看 C104 的【中國翻訳】了口牙

結尾廣告:中南大學 ACM 集訓隊絕贊招新中!

有資訊奧賽基礎,獲得 NOIP 省一等獎並達到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我與校隊隊長聯絡,免選拔直接進校集訓隊參加區域賽!

沒有達到該水平但有志於 XPCX 賽事請關注每學年開始的 ACM 校隊招新喵!

到這個時候了還缺隊友實在不妙!求求求求快來個大神帶我嗚嗚嗚嗚

相關文章