10.12模拟赛总结
总结
考场估分:\([20, 60] + [0, 100] + [40, 100] + [0, 45] = [60, 305]\)。
实际得分:\(100 + 100 + 50 + 0 = 250\),怎么感觉在骂我,与“积蚕鸭”机惨鸭并列第一/jy/jy/jy
今天爆搜场?!
\(\texttt{T1 switch}\)
题意
一个序列 \(a_1, a_2, \ldots, a_n\),求有没有一种可能使得 \(a_1 \pm a_2 \pm \ldots \pm a_n\) 的值重复。
分析
考场写了个爆搜然后过了/jy/jy/jy
爆搜思路
因为 \(n \times a_i\) 的值域在 \([0, 5 \times 10^{10}]\),所以根据鸽巢原理,当 \(n\) 大于某个值时答案一定为 \(\texttt{Yes}\),这个值大概是使得 \(2^n > 5 \times 10^5 \times n\) 的最小值,根据计算可得,这个值是 \(20\) 左右,所以当 \(n\) 大于等于 \(20\) 时直接输出 \(\texttt{Yes}\) 就行了,剩下的情况当然直接爆搜就没了。
注意的是,这题中深搜好像比宽搜快。(深搜 \(\texttt{148ms}\),宽搜 \(\texttt{505ms}\))
正解思路
哦,好像上面的思路就是正解。
寄因
没寄。
代码
爆搜代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int kMaxN = 1e5 + 5;
int n;
int b = clock();
ll a[kMaxN];
unordered_map<int, bool> vis;
queue<pair<int, pair<int, int>>> q;
void dfs(ll ind, ll sum, ll mus) {
  if (ind > n) {
    if (vis[sum - mus]) {
      cout << "Yes\n";
      // cerr << clock() - b << '\n';
      exit(0);
    }
    vis[sum - mus] = 1;
    return;
  }
  if (ind & 1) {                        // 一个玄学优化,不加也没事
    dfs(ind + 1, sum + a[ind], mus);
    dfs(ind + 1, sum, mus + a[ind]);
  } else {
    dfs(ind + 1, sum, mus + a[ind]);
    dfs(ind + 1, sum + a[ind], mus);
  }
}
void bfs() {
  q.push({1, {0, 0}});
  while (!q.empty()) {
    auto tp = q.front();
    q.pop();
    if (tp.first > n) {
      if (vis[tp.second.first - tp.second.second]) {
        cout << "Yes\n";
        exit(0);
      }
      vis[tp.second.first - tp.second.second] = 1;
      continue;
    }
    if (tp.first & 1) {
      q.push({tp.first + 1, {tp.second.first, tp.second.second + a[tp.first]}});
      q.push({tp.first + 1, {tp.second.first + a[tp.first], tp.second.second}});
    } else {
      q.push({tp.first + 1, {tp.second.first + a[tp.first], tp.second.second}});
      q.push({tp.first + 1, {tp.second.first, tp.second.second + a[tp.first]}});
    }
  }
}
int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  if (n >= 24) {
    cout << "Yes\n";
    return 0;
  }
  sort(a + 1, a + n + 1);
  // bfs();
  dfs(1, 0, 0);
  cout << "No\n";
  // cerr << clock() - b << '\n';
  return 0;
}
正解代码
点击查看代码
和爆搜一样。
\(\texttt{T2 game}\)
题意
有 \(n\) 堆石子,每堆石子有 \(2\) 块,如果这堆石子还剩 \(k\) 块,每次可以拿走 \(1 \sim k\) 块,每 \(3\) 次操作后,会增加一堆有 \(2\) 块的石子,求 \(\texttt{Ayano}\) 和 \(\texttt{Bob}\) 谁能赢(\(\texttt{Ayano}\))。
分析
找规律思路
观察样例找一下规律,充分发扬人类智慧,可以发现,当 \(\texttt{Bob}\) 能获胜有且仅有 \(n \equiv 2 \pmod 3\),而其它情况都是 \(\texttt{Ayano}\) 获胜。
正解思路
记忆化搜索,没了。
时间复杂度 \(\Theta()\)
寄因
没寄。
代码
找规律代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  if (n % 3 == 0 || n % 3 == 1) {
    cout << "Ayano\n";
  } else {
    cout << "Bob\n";
  }
  return 0;
}
正解代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 1e3 + 5;
int f[3][kMaxN][kMaxN][2], g[3][kMaxN][kMaxN][2], n;
int D(int step, int x, int y, int op) {
  if (!x && !y) {
    return op;
  } else if (g[step][x][y][op]) {
    return f[step][x][y][op];
  } else {
    g[step][x][y][op] = 1;
    int ns = (step + 1) % 3;
    if (op == 0) {
      if (x) {
        if (D(ns, x - 1, y + !ns, !op)) {
          return f[step][x][y][op] = 1;
        }
      }
      if (y) {
        if (D(ns, x + 1, y - 1 + !ns, !op)) {
          return f[step][x][y][op] = 1;
        } else if (D(ns, x, y - 1 + !ns, !op)) {
          return f[step][x][y][op] = 1;
        }
      }
      return f[step][x][y][op] = 0;
    } else {
      if (x) {
        if (!D(ns, x - 1, y + !ns, !op)) {
          return f[step][x][y][op] = 0;
        }
      }
      if (y) {
        if (!D(ns, x + 1, y - 1 + !ns, !op)) {
          return f[step][x][y][op] = 0;
        } else if (!D(ns, x, y - 1 + !ns, !op)) {
          return f[step][x][y][op] = 0;
        }
      }
      return f[step][x][y][op] = 1;
    }
  }
}
int main() {
  cin >> n;
  cout << (D(0, 0, n, 0) ? "Ayano" : "Bob");
  return 0;
}
\(\texttt{T3 buy}\)
题意
\(n\) 个物品,每个物品有一个价格 \(a_i\) 和满意度 \(b_i\),定义一些物品的总价格为 \(a_{id_1} \oplus a_{id_2} \oplus \ldots \oplus a_{id_n}\),总满意度为 \(a_{id_1} + a_{id_2} + \ldots + a_{id_n}\),在物品的总价格不超过 \(m\) 的情况下,求最大的总满意度。
分析
搜索加个最优性剪枝可过。
时间复杂度 \(\Theta(2^n)\) 跑不满。
寄因
没寄,爆搜打满了。
硬要说寄了的话就是没想到剪枝。
代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int kMaxN = 40;
long long n, m, maxx, sum[kMaxN];
struct node {
  long long a, b;
} a[kMaxN];
bool cmp(node a, node b) {
  return a.b < b.b;
}
void dfs(int k, long long num, long long x) {
  if (num + sum[k + 1] <= maxx) {
    return;
  }
  if (k == n) {
    if (x <= m) {
      maxx = max(maxx, num);
    }
    return;
  }
  k++;
  dfs(k, num + a[k].b, x ^ a[k].a);
  dfs(k, num, x);
}
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    cin >> a[i].a;
  }
  for (int i = 1; i <= n; i++) {
    cin >> a[i].b;
  }
  sort(a + 1, a + n + 1, cmp);
  for (int i = n; i >= 1; i--) {
    sum[i] = sum[i + 1] + max(a[i].b, 0ll);
  }
  dfs(0, 0ll, 0ll);
  cout << maxx;
  return 0;
}
\(\texttt{T4 match}\)
题意
给定一棵 \(n\) 个节点的树,对于每个点,要有另一个点与之匹配。
求满足以下条件的匹配的方案数对 \(998244353\) 取模的结果:
- 
这个匹配 \((u, v)\) 要么与另一个匹配 \((x, y)\) 相离,要么包含。
 - 
相离:\(u\) 到 \(v\) 的路径上,不与 \(x\) 到 \(y\) 的路径相交。
 - 
包含:\(u\) 到 \(v\) 的路径上包含 \(x\) 到 \(y\) 的路径。
 
分析
扬跃顶针,鉴定为纯纯的 dp。
设 \(f_{u, i}\) 表示在 \(u\) 的子树中做匹配,且从 \(u\) 出发的长度为 \(i\) 的链无法匹配。
可以发现 \(f_{v_1, i}\) 与 \(f_{v_2, j}\) 的合并要乘上一个长度为 \(i + 1 + j\) 的链匹配的方案数。
然后因为要求补充不漏,就令这条链的两个端点互相匹配,然后就充分发扬人类智慧的发现中间的方案数就是卡特兰数。
然后没了,时间复杂度 \(\Theta(n^2)\)。
寄因
某种意义上没寄。
代码
点击查看代码
还在咕咕咕呢!
                    
                
                
            
        
浙公网安备 33010602011771号