NordicOI 2023

A. ChatNOI

题目描述

给定一个由 \(N\) 个小写英文单词组成的文章,我们定义一个 \(k+1\) 个单词的可能性为其在文章中的出现次数。现在给出一个句子的前 \(k\) 个单词,你要补全后面的 \(m\) 个单词,使得其中所有长度为 \(k+1\) 的字串的可能性最小值最大。有 \(Q\) 次询问。

思路

因为我们是不断在后面加入单词,所以我们可以把这个文章变成一个图。每个的字串 \([l,l+k-1]\) 连向 \([l+1,l+k]\),边权为 \([l+1,l+k]\) 的可能性。在这个图上我们要找到一个长度为 \(m\) 的路径使得其边权最小值最大。

这里很显然点,边和边权总和的数量级都是 \(O(N)\) 的,而不同的边权数量是 \(O(\sqrt N)\) 的,因为最优情况下边权依次是 \(1,2,\dots,\sqrt N\)

这个有什么用呢?我们可以暴力从大到小枚举每种边权 \(x\),并把所有 \(\ge x\) 的边加入。令 \(u\) 为询问给出的句子对应的点,如果此时第一次从 \(u\) 开始可以行走至少 \(m\) 步,那么这个询问的答案为 \(x\),使用拓扑排序解决。

空间复杂度 \(O(N+Q)\),时间复杂度 \(O((N+Q)\sqrt N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;

const int MAXN = 500001, MAXQ = 100001, INF = INT_MAX;
const ll b[2] = {29, int(1e9) + 9}, MOD = int(1e9) + 7;

struct query {
  int m, id;
  ull x;
}a[MAXQ];

int n, k, tot, q, in[MAXN], dp[MAXN], f[MAXN], fid[MAXN];
ull sum[MAXN], Pow[MAXN];
string S[MAXN];
map<ull, int> mp, cnt;
vector<tuple<int, int, int>> e[MAXN];
vector<int> ve, ans[MAXQ];
vector<tuple<int, int, int, int>> vec;

int Hash(const string &s) {
  int ret = 0;
  for(char c : s) {
    ret = (1ll * ret * b[0] % MOD + c - 'a' + 1) % MOD;
  }
  return ret;
}

ull Calc(int l, int r) {
  return sum[r] - sum[l - 1] * Pow[r - l + 1];
}

void topo_sort() {
  queue<int> que;
  fill(in + 1, in + tot + 1, 0);
  for(int i = 1; i <= tot; ++i) {
    dp[i] = 0;
    for(auto [v, w, id] : e[i]) {
      in[v]++;
    }
  }
  for(int i = 1; i <= tot; ++i) {
    if(!in[i]) {
      que.push(i);
    }
  }
  for(; !que.empty(); ) {
    int u = que.front();
    que.pop();
    for(auto [v, w, id] : e[u]) {
      if(dp[u] + 1 > dp[v]) {
        dp[v] = dp[u] + 1, f[v] = u, fid[v] = id;
      }
      if(!(--in[v])) {
        que.push(v);
      }
    }
  }
  for(int i = 1; i <= tot; ++i) {
    if(in[i]) {
      dp[i] = INF;
      for(auto [v, w, id] : e[i]) {
        if(in[v]) {
          f[v] = i, fid[v] = id;
        }
      }
    }
  }
}

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> k;
  Pow[0] = 1;
  for(int i = 1; i <= n; ++i) {
    string s;
    cin >> s;
    S[i] = s;
    sum[i] = sum[i - 1] * b[1] + Hash(s);
    Pow[i] = Pow[i - 1] * b[1];
  }
  for(int i = 1; i <= n - k; ++i) {
    cnt[Calc(i, i + k)]++;
  }
  for(int i = 1; i <= n - k + 1; ++i) {
    ull h = Calc(i, i + k - 1);
    if(!mp.count(h)) {
      mp[h] = ++tot;
    }
    if(i > 1) {
      vec.emplace_back(cnt[Calc(i - 1, i + k - 1)], mp[Calc(i - 1, i + k - 2)], mp[h], i + k - 1);
      ve.emplace_back(cnt[Calc(i - 1, i + k - 1)]);
    }
  }
  cin >> q;
  for(int i = 1; i <= q; ++i) {
    cin >> a[i].m;
    for(int j = 1; j <= k; ++j) {
      string s;
      cin >> s;
      a[i].x = a[i].x * b[1] + Hash(s);
    }
    a[i].x = mp[a[i].x];
  }
  sort(ve.begin(), ve.end()), ve.erase(unique(ve.begin(), ve.end()), ve.end()), reverse(ve.begin(), ve.end());
  sort(vec.begin(), vec.end(), greater<tuple<int, int, int, int>>());
  int j = 0;
  for(int x : ve) {
    for(; j < int(vec.size()); ++j) {
      auto [w, u, v, id] = vec[j];
      if(w >= x) {
        e[v].emplace_back(u, w, id);
      }else {
        break;
      }
    }
    topo_sort();
    for(int i = 1; i <= q; ++i) {
      if(!a[i].x || ans[i].size() || dp[a[i].x] < a[i].m) {
        continue;
      }
      int u = a[i].x, cnt = 1;
      for(; cnt <= a[i].m; u = f[u], cnt++) {
        ans[i].emplace_back(fid[u]);
      }
    }
  }
  for(int i = 1; i <= q; ++i) {
    if(ans[i].empty()) {
      for(int j = 1; j <= a[i].m; ++j) {
        cout << S[1] << " \n"[j == a[i].m];
      }
    }else {
      for(int x : ans[i]) {
        cout << S[x] << " ";
      }
      cout << "\n";
    }
  }
  return 0;
}

B. Ice Cream Machines

题目描述

\(N\) 个人,第 \(i\) 个人想吃第 \(c_i\) 种口味的冰淇淋。有 \(k\) 个冰淇淋机,每种机器都可以制作任意口味的冰淇淋,但是每次让一个冰淇淋机制作和之前不同的冰淇凌都要清洗一次。第一次使用也要清洗。

求至少要清洗几次。

思路

用贪心解决。我们用 set 记录每个冰淇凌机当前是什么冰淇凌,该冰淇凌下次出现的位置,我们肯定会选择下次出现位置更靠后的清洗。

空间复杂度 \(O(N)\),时间复杂度 \(O(N\log N)\)

代码

#include<bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;

const int MAXN = 200001, MAXM = 200001, MAXK = 200001;

int n, m, k, c[MAXN], cnt[MAXM], a[MAXK], pos[MAXM], ans;
set<pii, greater<pii>> s;
vector<int> ve[MAXM];

int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m >> k;
  for(int i = 1; i <= n; ++i) {
    cin >> c[i];
    ve[c[i]].emplace_back(i);
  }
  for(int i = 1; i <= m; ++i) {
    ve[i].emplace_back(n + 1);
  }
  for(int i = 1; i <= k; ++i) {
    s.insert({n + 1, i});
  }
  for(int i = 1; i <= n; ++i) {
    if(!pos[c[i]]) {
      ans++;
      auto [cur, p] = *s.begin();
      s.erase(s.begin());
      s.insert({*upper_bound(ve[c[i]].begin(), ve[c[i]].end(), i), p});
      pos[a[p]] = 0;
      pos[c[i]] = p;
      a[p] = c[i];
    }else {
      s.erase({i, pos[c[i]]});
      s.insert({*upper_bound(ve[c[i]].begin(), ve[c[i]].end(), i), pos[c[i]]});
    }
  }
  cout << ans;
  return 0;
}
posted @ 2024-10-18 10:35  Yaosicheng124  阅读(5)  评论(0编辑  收藏  举报