模拟赛总结

导读

由于好渴鹅过于智慧,因此这段时间一直在掉大分,因此写篇博客压压惊。

题目

救生员(lifeguards)

这题灰常的简单,每一个线段被取消之后只会丢失这个线段与其他线段不重复的部分,因此排完序我们就可以直接贪心。写一个函数求出线段最小的自己所有的部分,注意要对 \(0\)\(\max\)

#include <iostream>
#include <algorithm>

using namespace std;
using ll = long long;

const ll kMaxN = 1e6 + 5;

struct Node {
  ll l, r;
} a[kMaxN];
ll n, p, s, ans;

ll solve() {
  ll s = 1e9;
  for (ll i = 1; i <= n; i++) {
    if (a[i].r <= a[i - 1].r || a[i + 1].l <= a[i - 1].r) {
      return 0;
    }
    s = min(s, min(a[i + 1].l, a[i].r) - max(a[i - 1].r, a[i].l));
  }
  return s;
}

int main() {
  cin >> n;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i].l >> a[i].r;
  }
  sort(a + 1, a + n + 1, [](const Node &a, const Node &b) {
    return a.l < b.l || (a.l == b.l && a.r < b.r);
  });
  a[0].r = a[1].l, a[n + 1] = {a[n].r, (ll)1e9};
  p = a[1].l;
  for (ll i = 1; i <= n; i++) {
    s += max(0LL, a[i].r - max(a[i].l, p));
    p = max(p, a[i].r);
  }
  cout << s - solve();
  return 0;
}

牛奶桶(pails)

直接设 \(dp_i\)\(dp_i\in 0,1\)) 表示是否可以使用 \(i\) 的牛奶,状态转移方程:

\[dp_i=\max(dp_{i-x},dp_{i-y}) \]

#include <iostream>

using namespace std;

int x, y, m;

int main() {
  cin >> x >> y >> m;
  dp[0] = 1;
  for (int i = 1; i <= n; i++) {
    i >= x && (dp[i] |= dp[i - x]);
    i >= y && (dp[i] |= dp[i - y]);
  }
  cout << (dp[m] ? "Yes" : "No") << '\n';
  return 0;
}

奶牛租赁(rental)

我们知道让产奶量最少的奶牛卖出去是可以获得更多钱的,那么我们先将奶牛排序,排完序之后就变成了枚举中间点,一半卖出、一半卖奶的问题了。我们可以使用前缀和以及后缀和进行维护。一定要枚举 \(0\) 啊啊啊啊!

永远怀念好渴鹅的 90 分

#include <iostream>
#include <algorithm>

using namespace std;
using ll = long long;

const ll kMaxN = 1e5 + 5;

struct Node {
  ll q, p;
} d[kMaxN];
ll a[kMaxN], v[kMaxN], sv[kMaxN], sd[kMaxN], n, m, r, ans;

int main() {
  cin >> n >> m >> r;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
  }
  sort(a + 1, a + n + 1);
  for (ll i = 1; i <= m; i++) {
    cin >> d[i].q >> d[i].p;
  }
  sort(d + 1, d + m + 1, [](const Node &a, const Node &b) {
    return a.p < b.p;
  });
  for (ll i = 1; i <= r; i++) {
    cin >> v[i];
  }
  sort(v + 1, v + r + 1, greater<ll>());
  // 预处理前缀和
  for (ll i = 1; i <= n; i++) {
    sv[i] = sv[i - 1] + (i <= r ? v[i] : 0);
  }
  // 预处理后缀和
  ll t = m;
  for (ll i = n; i >= 1; i--) {
    sd[i] = sd[i + 1];
    while (t >= 1) {
      if (a[i] >= d[t].q) {
        sd[i] += d[t].q * d[t].p, a[i] -= d[t].q, t--;
      } else {
        sd[i] += a[i] * d[t].p, d[t].q -= a[i], a[i] = 0;
        break;
      }
    }
  }
  // 处理每头奶牛
  for (ll i = 0; i <= n; i++) {
    ans = max(ans, sv[i] + (sd[i + 1] - sd[n + 1]));
  }
  cout << ans << '\n';
  return 0;
}

MooTube(mootube)

一道大水题。我先我们看到这个 \(1\le N,Q\le 5000\),然后进行 dfs 暴力。我们进行一个小小的剪枝优化:当前如果已经无法推荐了,那么直接 return,这样子就可以暴力卡过。

#include <iostream>
#include <utility>
#include <vector>

#define add(u, v, w) e[u].push_back({v, w})

using namespace std;
using ll = long long;

const ll kMaxN = 5005;

ll n, q, k, u, ans;
vector<pair<ll, ll>> e[kMaxN];

void dfs(ll x, ll f, ll s) {
  if (s < k) {
    return;
  }
  ans++;
  for (auto i: e[x]) {
    if (i.first != f) {
      dfs(i.first, x, min(s, i.second));
    }
  }
}

int main() {
  cin >> n >> q;
  for (ll i = 1, u, v, w; i < n; i++) {
    cin >> u >> v >> w;
    add(u, v, w), add(v, u, w);
  }
  for (ll i = 1; i <= q; i++) {
    cin >> k >> u, ans = 0;
    dfs(u, 0, 1e9);
    cout << ans - 1 << '\n';
  }
  return 0;
}

密码代码(scode)

我们可以从结束状态直接进行大暴力分治。设 \(f(s)\) 表示当前的字符串 \(s\) 有多少种方案,如果还没有求解,那么就进行递归求解,否则就直接调用之前的记忆。其实不用记忆化也可以卡过的。。。

#include <iostream>
#include <string>
#include <map>
#include <set>

using namespace std;

const int kMod = 2014;

string s;
map<string, int> dp;

int dfs(string s) {
  if (s.size() <= 1) {
    return 0;
  }
  if (dp.count(s)) {
    return dp[s];
  }
  int ans = 1;
  for (int i = 0; i < s.size() - 1; i++) {
    string a = s.substr(0, i + 1);
    string b = s.substr(i + 1, s.size() - i - 1);
    if (a.size() > b.size()) {
      if (a.substr(0, b.size()) == b) {
        ans += dfs(a), ans %= kMod;
      }
      if (a.substr(a.size() - b.size(), b.size()) == b) {
        ans += dfs(a), ans %= kMod;
      }
    }
    if (b.size() > a.size()) {
      if (b.substr(0, a.size()) == a) {
        ans += dfs(b), ans %= kMod;
      }
      if (b.substr(b.size() - a.size(), a.size()) == a) {
        ans += dfs(b), ans %= kMod;
      }
    }
  }
  return dp[s] = ans;
}

int main() {
  cin >> s;
  dfs(s);
  cout << dp[s] - 1 << '\n';
  return 0;
}

隔离

题目看着十分困难,其实就只有两种方案:

  • 把题目在 B 地全部做完在回来并隔离;
  • 每次快要到隔离的时候回去一趟。

注意:如果已经有一个工作超过了 \(240\) 小时,那么直接一口气做完。

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 1e5 + 5;

ll a[kMaxN], n, s, c, p = 400;

int main() {
  cin >> n;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (ll i = 1; i <= n; i++) {
    s += a[i];
  }
  for (ll i = 1; i <= n; i++) {
    if (a[i] >= 240) {
      p = 2e18;
      break;
    }
    if (c + a[i] >= 240) {
      c = 0, p += a[i] + 400, c += a[i];
    } else {
      c += a[i], p += a[i];
    }
  }
  cout << min(s + 10080 + 400, p) << '\n';
  return 0;
}

和积

我们直接预处理 \([1,5\times 10^6]\) 的每个数的各个数位的和与积,然后进行大暴力,开上 O2,信仰提交!!!TLE A 了!

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 5e6 + 5;

ll s[kMaxN], t[kMaxN], T, m, n, k, ans, sol;

ll sum(ll x) {
  ll s = 0;
  for (; x; x /= 10) {
    s += x % 10;
  }
  return s;
}

ll times(ll x) {
  ll s = x % 10;
  for (x /= 10; x; x /= 10) {
    s *= x % 10;
  }
  return s;
}

void solve(ll n) {
  for (ll i = 1; i <= n; i++) {
    s[i] = sum(i), t[i] = times(i);
  }
}

int main() {
  solve(5e6);
  for (cin >> T; T; T--) {
    cin >> m >> n >> k, ans = sol = -1;
    for (ll i = m; i <= n; i++) {
      if (s[i] == k && t[i] > sol) {
        ans = i, sol = t[i];
      }
    }
    cout << ans << ' ' << sol << '\n';
  }
  return 0;
}

对联

我们可以用 set 存下每一个数是否出现过,然后使用 map 记录下每一个数对应的数。对于上联的每一个数,如果已经记录下来了就直接输出;否则继续往大的枚举直到没有出现,记录并输出。

#include <iostream>
#include <set>
#include <unordered_map>

using namespace std;

const int kMaxN = 1e5 + 5; 

int a[kMaxN], n, l;
set<int> s;
unordered_map<int, int> f;

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i], s.insert(a[i]);
  }
  for (int i = 1; i <= n; i++) {
    if (f.count(a[i])) {
      cout << f[a[i]] << ' ';
    } else {
      for (l++; s.count(l); l++) {
      }
      cout << (f[a[i]] = l) << ' ';
    }
  }
  return 0;
}

跳跃的排列

仔细观察数据,我们可以发现规律:不是输出 \(1\) 就是输出 \(0\)。因此我们可以得出结论:如果有任意一个顺序对那么就输出 \(1\),否则输出 \(0\)。这样子是 \(\mathcal{O}(n\log_2 n)\) 的,但是这题 \(1\le n\le 10^6\),复杂度可能被卡,因此我们直接暴力枚举:

对于每一个 \(1< i\le n\),如果 \(a_{i-1}<a_i\) 那么直接输出 \(1\),否则输出 \(0\)

#include <iostream>
#include <algorithm>

using namespace std;

const int kMaxN = 1e6 + 5;

int a[kMaxN], n, ans;

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (int i = 2; i <= n; i++) {
    if (a[i] > a[i - 1]) {
      cout << "1\n";
      return 0;
    }
  }
  cout << "0\n";
  return 0;
}

智力测试题

过于可爱的题目。。。 直接模拟!!!

#include <cstring>
#include <iostream>
#include <string>

using namespace std;

int n, t, x = 1, y = 1;

int main() {
  cin >> n >> t;
  for (int i = 1; i <= t; i++) {
    if (x != n || y != n) {
      y++, (y > n ? x++, y = 1 : 114514);
    } else {
      x = y = 1;
    }
  }
  cout << x << ' ' << y << '\n';
  return 0;
}

防御法阵

我们首先对于每一个城墙进行一个简单的 01 背包,\(t\) 即为重量限制。设 \(dp_i\) 表示使用了 \(i\) 的时间最多能够获得的经验值,状态转移教程:

\[dp_j=\max(dp_j,dp_{j-w_i}+v_i) \]

然后对于每一个城墙,分别求一遍 01 背包,进行序列 \(dp\)。设 \(dp_{(i,j)}\) 表示当前已经破坏掉了 \([i,j]\) 的城墙,所能获得的最大经验值,然后进行转移:

\[dp_{(i-1,j)}=\max{(dp_{(i-1,j)}, dp_{(i,j)} + f_{i-1})}(i>1) \]

\[dp_{(i,j+1)}=\max{(dp_{(i,j+1)}, dp_{(i,j)} + f_{i+1})}(i<n) \]

但是这样子 \(N\times N\) 的数组就会逝世,因为 MLE 了。我们知道每次 \(j-i+1\) 最大只能是 \(M\),因此时间复杂度是 \(M^2\) 的,但是我们又懒得写偏移,因此可以直接使用 map 进行转移,然后开 O2 卡过。原谅我加了火车头……

网站的可爱评测机竟然没有卡过!

#include <iostream>
#include <cstring>
#include <map>
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")

using namespace std;
using ll = long long;

const ll kMaxN = 10005, kMaxK = 55, kMaxT = 205;

ll v[kMaxK], w[kMaxK], dp[kMaxN][kMaxT], f[kMaxN], pre[kMaxN], n, m, t, k, ans;
map<pair<ll, ll>, ll> DP;

void S() {
  for (ll i = 1; i <= n; i++) {
    f[i] = dp[i][t];
  }
}

ll DymanicProgram(ll x) {
  DP.clear();
  DP[{x, x}] = f[x];
  for (int i = 1; i < m; i++) {  // 当前区间长度
    for (int j = x - m + 1; j <= x; j++) {  // 枚举端点
      if (j + i - 1 >= x) {
        int l = j, r = j + i - 1;  // 左端点和右端点
        if (l > 1) {
          DP[{l - 1, r}] = max(DP[{l - 1, r}], DP[{l, r}] * 2 + f[l - 1]);
          DP[{l, r + 1}] = max(DP[{l, r + 1}], DP[{l, r}] * 2 + f[r + 1]);
        }
        if (r < n) {
          DP[{l, r + 1}] = max(DP[{l, r + 1}], DP[{l, r}] * 2 + f[r + 1]);
        }
      }
    }
  }
  ll mx = 0;
  for (int i = max(1ll, x - m + 1); i <= x && i + m - 1 <= n; i++) {
    mx = max(mx, DP[{i, i + m - 1}]);
  }
  return mx;
}

int main() {
  cin >> n >> m >> t;
  for (ll i = 1; i <= n; i++) {
    cin >> k;
    for (ll j = 1; j <= k; j++) {
      cin >> v[j] >> w[j];
    }
    for (ll j = 1; j <= k; j++) {
      for (ll l = t; l >= w[j]; l--) {
        dp[i][l] = max(dp[i][l], dp[i][l - w[j]] + v[j]);
      }
    }
  }
  S();
  for (ll i = 1; i <= n; i++) {
    ans = max(ans, DymanicProgram(i));
  }
  cout << ans << '\n';
  return 0;
}

采集原石

对于每一个原石,如果它可以获得更多原石,就加入 vector 以所需原石进行排序,因为不管它能获得多少的原石,只要它是能赚的,那么所需原石越少就可以越早买到。排完序直接贪心就可以了。

#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>

using namespace std;
using ll = long long;

ll n, k;
vector<pair<ll, ll>> v;

int main() {
  cin >> n >> k;
  for (ll i = 1, k, m; i <= n; i++) {
    cin >> k >> m, k > m && (v.push_back({k, m}), 114514);
  }
  sort(v.begin(), v.end(), [](auto a, auto b) {
    return a.second < b.second;
  });
  for (auto i : v) {
    if (k < i.second) {
      break;
    }
    k -= i.second, k += i.first;
  }
  cout << k << '\n';
  return 0;
}

字母简记

我们枚举每一个字符,如果是数字就把后面连起来的数字进行计算,加上当前的字符串,然后使用循环进行重复就行了。

#include <iostream>

using namespace std;

int t, n, l;
string s, ans, tmp;

int main() {
  for (cin >> t; t; t--) {
    cin >> n >> s, l = 0, ans = "";
    for (int i = 0; i < n; i++) {
      if (isdigit(s[i])) {
        int c = 0, p = i;
        for (; i < n && isdigit(s[i]); i++) {
          c = c * 10 + s[i] - 48;
        }
        ans += s.substr(l, p - l), tmp = ans;
        for (int i = 1; i < c; i++) {
          ans += tmp;
        }
        l = i;
      }
    }
    cout << ans << '\n';
  }
  return 0;
}

攻与防

过于智慧的前缀和水题。考场上,他们都在说是二分什么的,我真 脏话文明 服了,这明明就是前缀和好不好???设 \(s_i\) 啊不,\(s_i\) 就是 \(\sum\limits_{j=1}^{s_i}a_j\),那么获得 \(\sum\limits_{j=p_1}^{p_2}a_j\) 我们就可以直接计算 \(s_{p_2}-s_{(p_1-1)}\) 就行了,这样就在 \(\mathcal{O}(1)\) 的时间内计算好了任意区间的静态前缀和。

#include <iostream>
#include <cmath>

using namespace std;
using ll = long long;

const ll kMaxN = 1e5 + 5;

ll n, p1[kMaxN], p2[kMaxN], ans = 1e9;
char c;

int main() {
  cin >> n;
  for (ll i = 1; i <= n; i++) {
    cin >> c;
    p1[i] = p1[i - 1] + (c == '0') * i;
    p2[i] = p2[i - 1] + (c == '1') * i;
  }
  for (ll i = 0; i <= n; i++) {
    ans = min(ans, abs(p1[i] - (p2[n] - p2[i])));
  }
  cout << ans << '\n';
  return 0;
}

四月是你的谎言

这题考试时好渴鹅脑抽了,写的正向贪心,WA 了一部分。其实我们在截取字符串的是否将顺序改为逆序就行了,贪心正确性请读者自己想,因为好渴鹅也不知道具体是什么原理

千万要注意 size_t 不能计算负数!!!

#include <iostream>

using namespace std;

const int kMaxN = 1e5 + 5;

int t, n;
string s[kMaxN], str;

int main() {
  for (cin >> t; t; t--) {
    cin >> n;
    for (int i = 1; i <= n; i++) {
      cin >> s[i];
    }
    cin >> str;
    for (int i = 0; i < str.size(); i++) {
      for (int j = 1; j <= n; j++) {
        if (i - (int)s[j].size() + 1 >= 0 && str.substr(i - s[j].size() + 1, s[j].size()) == s[j]) {
          str[i] = '*';
        }
      }
    }
    cout << str << '\n';
  }
  return 0;
}

求余来喽

直接暴力枚举。

#include <iostream>

using namespace std;
using ll = long long;

const ll kMaxN = 3005;

ll a[kMaxN], n, l, r, mx = 1e9, ans;

int main() {
  cin >> n >> l >> r;
  for (ll i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (ll k = l; k <= r; k++) {
    ll s = 0;
    for (ll i = 1; i <= n; i++) {
      s += a[i] % k;
    }
    if (s < mx) {
      mx = s, ans = k;
    }
  }
  cout << ans << '\n';
  return 0;
}

乘法考验

直接贪心就行。先考虑万能的情况,如果需要 \(k\)\(0\) 那么 \(b\) 就等于 \(10^{k-\log_{10}a}\),此时我们知道 \(10\) 的因数有 \(2\)\(5\),所以 \(k\) 可以除以若干个 \(2\)\(5\)。我们对 \(a\) 进行对 \(2\)\(5\) 分解只因数。设 \(x\)\(y\) 表示 \(a\)\(2\)\(y\) 分解质因数的次数。答案取 \(2^x+5^y\) 就行了。记得判断负数的情况。

#include <iostream>

using namespace std;
using ll = long long;

ll t, a, k, ans, t1, t2, f;

int main() {
  for (cin >> t; t; t--) {
    cin >> a >> k, t1 = t2 = 0, ans = 1, f = 1;
    if (a < 0) {
      f = -1, a = -a;
    }
    for (ll i = 1; i <= k && a % 2 == 0; i++) {
      a /= 2, t1++;
    }
    for (ll i = 1; i <= k && a % 5 == 0; i++) {
      a /= 5, t2++;
    }
    for (ll i = 1; i <= k - t1; i++) {
      ans *= 2;
    }
    for (ll i = 1; i <= k - t2; i++) {
      ans *= 5;
    }
    cout << ans * f << '\n';
  }
  return 0;
}

回文树

我们知道,一个结点的改变,只会改变父结点,因此我们可以想出一个算法:不停的遍历父结点,并与之前的的结果进行比较,这样子就少了一次 \(\mathcal{O}(n)\) 的遍历。总时间复杂度 \(\mathcal{O}(q\log_2 n)\)

#include <iostream>

using namespace std;

const int kMaxN = 1e5 + 5;

int f[kMaxN], t[kMaxN * 4][27], s[kMaxN], n, q, ans;
char a[kMaxN], c;

bool check(int x) {
  int c = 0;
  for (int i = 0; i < 26; i++) {
    c += t[x][i] & 1;
  }
  return (s[x] & 1 ? c == 1 : !c);
}

void build(int x) {
  t[x][a[x] - 'a']++, s[x]++;
  if (x * 2 <= n) {
    build(x * 2), s[x] += s[x * 2];
  }
  if (x * 2 + 1 <= n) {
    build(x * 2 + 1), s[x] += s[x * 2 + 1];
  }
  for (int i = 0; i < 26; i++) {
    t[x][i] += t[x * 2][i] + t[x * 2 + 1][i];
  }
}

void update(int x, char c) {
  char lst = a[x];
  a[x] = c;
  for (int i = x; i != 1; i /= 2) {
    t[i][lst - 'a']--, t[i][c - 'a']++;
  }
  t[1][lst - 'a']--, t[1][c - 'a']++;
}

void query(int x) {
  for (int i = x; i != 1; i /= 2) {
    int p = check(i);
    if (p) {
      ans += (f[i] != 1);
    } else {
      ans -= (f[i] == 1);
    }
    f[i] = p;
  }
  int p = check(1);
  if (p) {
    ans += (f[1] != 1);
  } else {
    ans -= (f[1] == 1);
  }
  f[1] = p;
}

int main() {
  cin >> n >> q;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  build(1);
  for (int i = 1; i <= n; i++) {
    if (check(i)) {
      f[i] = 1, ans++;
    }
  }
  cout << ans << '\n';
  for (int x; q; q--) {
    cin >> x >> c;
    update(x, c);
    query(x);
    cout << ans << '\n';
  }
  return 0;
}

完结散花!

posted @ 2023-10-19 14:39  haokee  阅读(36)  评论(0)    收藏  举报