2024牛客暑期多校訓練營8

Luckyblock發表於2024-08-09

目錄
  • 寫在前面
  • K
  • A
  • E
  • J
  • D
  • G
  • I
  • 寫在最後

寫在前面

比賽地址:https://ac.nowcoder.com/acm/contest/81603

以下按個人難度向排序。

dztlb 大神回去補辦身份證了,於是單刷,還是打的像史。

哎呦 E 的最佳化方向錯了把 108 最佳化沒了變成跑滿的根號我也是真牛逼

K

簽到,DP。

發現合成合法字串過程中往左右加字元是無所謂的。字串合法等價於字串由若干 avaavava 按順序連線得到,於是直接 DP 判斷即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, f[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::string s; std::cin >> s;
    n = s.length();
    s = "$" + s;
    for (int i = 1; i <= n; ++ i) f[i] = 0;
    f[0] = 1;

    int flag = 0;
    for (int i = 1; i <= n; ++ i) {
      if (s[i] != 'a' && s[i] != 'v') flag = 1;
      if (i >= 3 && s.substr(i - 2, 3) == "ava") f[i] |= f[i - 3];
      if (i >= 5 && s.substr(i - 4, 5) == "avava") f[i] |= f[i - 5];;
    }
    if (flag || f[n] == 0) std::cout << "No\n";
    else std::cout << "Yes\n";
  }
  return 0;
}

A

數論。

哈哈這題真熟寫的時候就感覺好像什麼時候肯定寫過一樣,果然是做過的原:CF1627D

由算數基本定理有 \(\gcd\) 的傳遞性:\(\gcd(x, \gcd(y, z)) = \gcd(\gcd(x, y), z) = \gcd(x, y, z)\),實際上問題變為求從原陣列中任意多個元素的 \(\gcd\) 的種類數(滿足不在原陣列中),若新增種類數為奇數則先手必勝,否則後手必勝。

套路地列舉 \(i = \gcd\),檢查是否有原陣列中 \(i\) 的倍數的 \(\gcd =i\),實現時僅需列舉 \(i\) 的在原陣列中出現的所有倍數,並檢查它們除掉 \(i\) 後是否有 \(\gcd=1\) 即可。

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

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, nowtime, a[kN], tim[kN], cnt[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;
    int maxa = 0;
    ++ nowtime;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i];
      maxa = std::max(maxa, a[i]);
      if (tim[a[i]] != nowtime) tim[a[i]] = nowtime, cnt[a[i]] = 0;
      cnt[a[i]] = cnt[a[i]] + 1;
    }

    int ans = 0;
    for (int d = 1; d <= maxa; ++ d) {
      if (tim[d] != nowtime) tim[d] = nowtime, cnt[d] = 0;
      if (cnt[d]) continue;
      int c = 0, dd = 0;
      for (int i = 2; d * i <= maxa; ++ i) {
        if (tim[d * i] != nowtime) tim[d * i] = nowtime, cnt[d * i] = 0;
        if (cnt[d * i]) {
          c += cnt[d * i]; 
          if (dd == 0) dd = i;
          else dd = std::__gcd(dd, i);
        }
      }
      if (dd == 1 && c >= 2) ++ ans;
    }
    std::cout << ((ans % 2 == 1) ? "dXqwq" : "Haitang") << "\n";
  }
  return 0;
}

E

數論,篩法。

實際區間篩板題!

發現有 \(S(m)\le 9\times 12\),上界在 \(m = 10^{12} - 1\) 時取到,於是考慮直接列舉 \(S(m) = n \bmod m\) 並檢查有多少 \(m\) 滿足 \(m = S(m)\)\(m | n - S(m)\)

考慮暴力實現,直接對 \(n-108 \sim n - 1\) 這 108 個數進行因數分解,並檢查所有因子能否作為有貢獻的 \(m\) 即可。大力上 Pollard Rho 質因數分解之後列舉所有因數可以卡過去,但這太呃呃了一點也不優美。

發現 \([n - 108, n - 1]\) 構成一段連續的區間,且 \(n\le 10^{12}\),它們至多僅有一個大於 \(\sqrt{n} = 10^6\) 的質因數,於是考慮區間篩對他們進行質因數分解。具體地:

  • 預處理不大於 \(\sqrt{n} = 10^6\) 的所有質數。
  • 對於每次詢問,列舉 \(\sqrt{n}=10^6\) 的所有因數 \(d\),求得它們在區間 \([n - 108, n - 1]\) 中的所有倍數,並對這些倍數不斷除 \(d\) 並記錄次數。
  • 列舉結束後,特判 \([n - 108, n - 1]\) 中所有數的至多一個大於 \(\sqrt{n} = 10^6\) 的質因數。

即可在 \(O\left(\frac{\sqrt{n}}{\log n}\right)\) 時間複雜度內完成對所有數的質因數分解,之後再大力列舉因數並檢查即可,檢查部分需要列舉所有因數並大力計算 \(S(m)\),時間複雜度為 \(O(d(n) \log v)\) 級別,\(\log v\le 12\)

總時間複雜度 \(O\left(T\left(\frac{\sqrt{n}}{\log n} + d(n) \log v\right)\right)\) 級別。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
const int N = 1e6;
//=============================================================
LL n, ans;
int pnum;
LL p[kN];
bool vis[kN];
std::vector<LL> value, pri[108], cnt[108]; 
//=============================================================
LL calc(LL m_) {
  LL ret = 0;
  while (m_ > 0) {
    ret += m_ % 10ll; 
    m_ /= 10ll;
  }
  return ret;
}
void init() {
  for (int i = 2; i <= N; ++ i) {
    if (!vis[i]) p[++ pnum] = i;
    for (int j = 2; i * j <= N; ++ j) {
      vis[i * j] = 1;
    }
  }
}
void dfs(int id_, LL now_, LL d_, LL sm_) {
  if (now_ >= pri[id_].size()) {
    if (d_ > sm_ && calc(d_) == sm_) ++ ans;
    return ;
  }
  for (int i = 0; i <= cnt[id_][now_]; ++ i) {
    dfs(id_, now_ + 1, d_, sm_);
    d_ *= pri[id_][now_];
  }
}
void solve() {
  value.clear();
  for (int i = 108; i; -- i) {
    value.push_back(n - i);
    pri[i - 1].clear();
    cnt[i - 1].clear();
  }
  for (int i = 1; i <= pnum; ++ i) {
    if (p[i] >= n) break;
    for (LL j = ceil(1.0 * (n - 108) / p[i]); j <= n / p[i]; ++ j) {
      if (n - 108 <= p[i] * j && p[i] * j < n) {
        LL v = p[i] * j - n + 108;
        value[v] /= p[i];
        pri[v].push_back(p[i]);
        cnt[v].push_back(1);
        while (value[v] % p[i] == 0) {
          ++ cnt[v].back();
          value[v] /= p[i];
        }
      }
    }
  }
  for (int i = 0; i < 108; ++ i) {
    if (value[i] != 1) pri[i].push_back(value[i]), cnt[i].push_back(1);
    dfs(i, 0, 1, 108 - i);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  init();

  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    ans = 0;
    
    if (n <= 108) {
      for (LL m = 1; m <= n; ++ m) if (n % m == calc(m)) ++ ans;
    } else {
      solve();
    }
    std::cout << ans << "\n";
  }
  return 0;
}

J

構造。

和題解一樣的構造方法,這裡重點記錄下實現。

首先考慮特殊情況,發現對於 \(1, 2, \cdots, n\) 可以構成 \(n-3\) 個三角形而達到上界;\(m=n-2\) 時無解因為 1 與任何數都無法構成三角形;令 \(d = \left\lfloor\frac{n}{3}\right\rfloor\),手玩下發現當有類似如下斐波那契數列的形式時可取到 \(m=0\)

\[[d, 2d, 3d],[d - 1, 2d - 1, 3d - 1], \cdots, [2, d+2, 2d+2], [1, d+1, 2d+1] \]

\(n\) 不為 3 的倍數,僅需在兩側分別加上 \(n - 2 = 3d + 1\)\(n-1 = 3d+ 2\) 即可:

\[3d + 1, [d, 2d, 3d],[d - 1, 2d - 1, 3d - 1], \cdots, [2, d+2, 2d+2], [1, d+1, 2d+1], 3d + 2 \]

然後考慮 \(0<m< n-3\) 時如何構造。一個顯然的想法是將上述兩種分別達到上界和下界的做法拼起來即可。首先選擇 \(1\sim n-m\) 構造成 \(m=0\) 的形式並放在數列開頭,然後將剩下的 \(n-m+1 \sim n\) 順序排列到數列後面即可。

發現此時 \((n - m)\bmod 3 \not= 1\) 的情況即可順利解決,可以保證前 \(n-m\) 個數無法構成任何三角形,後面 \(m+2\) 個數均可構成三角形;但對於 \((n - m)\bmod 3 = 1\) 的情況,可能導致恰好有 \((d + 1) + (2d + 1) \le n-m+1\),此時數列中的三角形數還缺不多於 2 個,需要考慮怎麼再湊出一些三角形。

我的做法是考慮到 1 和任何數都無法構成三角形,且在上述 \(m=0\) 的構造中 1 並不位於兩端,導致阻礙了至少 3 個三角形的構成。又發現 1 左移一位可以增加 1 個三角形,冒泡到最左端可以增加 2 個三角形,特判下即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 3e5 + 10;
//=============================================================
int n, m, ans[kN], vis[kN];
//=============================================================
int calc() {
  int ret = 0;
  for (int i = 1; i <= n - 2; ++ i) {
    std::vector<int> tri{ans[i], ans[i + 1], ans[i + 2]};
    std::sort(tri.begin(), tri.end());
    if (tri[0] + tri[1] > tri[2]) ++ ret;
  }
  return ret;
}
void solve() {
  for (int i = 1; i <= n; ++ i) vis[i] = 0;

  std::vector<int> temp;
  int d = (n - m) / 3;
  if (3 * d + 1 <= (n - m)) temp.push_back(3 * d + 1);
  for (int i = 0; i <= d - 1; ++ i) {
    for (int j = 1; j <= 3; ++ j) {
      temp.push_back(j * d - i);
    }
  }
  if (3 * d + 2 <= (n - m)) temp.push_back(3 * d + 2);

  int p = 0;
  for (auto x: temp) ans[++ p] = x;
  if (m == 0) return ;
  
  for (int i = n - m + 1; i <= n; ++ i) ans[++ p] = i;
  
  int pos1 = 0;
  for (int i = 1; i <= n; ++ i) if (ans[i] == 1) pos1 = i;

  if ((n - m) % 3 == 1) {
    int c = calc();
    if (c == m - 1) {
      std::swap(ans[pos1], ans[pos1 - 1]);
    } else if (c == m - 2) {
      for (int i = pos1; i; -- i) std::swap(ans[i], ans[i - 1]);
    }
  }
}
//=============================================================
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;
    if (m >= n - 2) {
      std::cout << -1 << "\n";
      continue;
    }
    solve();
    for (int i = 1; i <= n; ++ i) std::cout << ans[i] << " ";
    std::cout << "\n";
  }
  return 0;
}
/*
*/

D

大模擬。

鑑定為玩賽馬娘 Pretty Derby 玩的哈哈坐牢做紅溫了還要出成題丟給五千個人一塊坐牢實在是拖累那之鑑!

幸好我是高貴的賽馬娘 Pretty Derby 玩家賽時不看題就直接秒了,但是單刷沒心情也沒時間寫,於是直接跑路。

G

DP。

有趣的 DP 狀態設計最佳化。

I

資料結構。

寫在最後

學到了什麼:

  • E:區間篩!
  • J:考慮分別達到上下界的構造,然後考慮構造的結合。

唉單刷太累了媽的兩場牛客都打得一坨希望今天杭電能少吃點。

相關文章