中国地质大学(武汉)2024年新生赛(同步赛)
发现没几个人写这场比赛的题解,顺便给补题的人提供一点思路,故而火速出了这篇(不会都去打区域赛了吧,悲~)
A
点击查看代码
void solve() {
int n;
cin >> n;
cout << n - 1 << '\n';
}
B
模拟题
根据题意:
一、预约:
考虑为0的情况:1.此时读者有书 2.读者上次预约时间未超过d天
其余情况为1
二、借书
首先考虑预约情况,如果当天有预约先解决预约的读者,因此我们需要borp[t]数组表示第t天预约状况
考虑不能借到情况:1.箱子里没有书 2.已经有了一本书
否则输出书堆顶部的书
三、还书
记得清除
四、查看
res:预约
borp:预约借书
bor:借书
ret:还书
que:查询
细节注意:顺序不可搞错
点击查看代码
struct node {
int book, res, d;
};
struct node1 {
int id, d, idx;
};
void solve() {
int n, m;
cin >> n >> m;
vector<int> stack(n + 1);
iota(stack.begin(), stack.end(), 0ll);
vector<node> a(N);
vector<vector<node1>> res(N), bor(N), ret(N), que(N), borp(N);
for (int i = 1; i <= m; i ++) {
int t, id, d;
string s;
cin >> t >> s >> id;
if (s == "RESERVE") {
cin >> d;
res[t].push_back({id, d, i});
}
if (s == "BORROW") bor[t].push_back({id, 0, i});
if (s == "RETURN") ret[t].push_back({id, 0, i});
if (s == "QUERY") que[t].push_back({id, 0, i});
}
vector<int> ans(m + 1);
for (int i = 1; i <= 1000; i ++) {
for (auto [id, d, idx] : res[i]) {
if(a[id].book || a[id].res && i - a[id].res < a[id].d) {
ans[idx] = 0;
}
else {
ans[idx] = 1;
borp[i + d].push_back({id, d, 0});
a[id].res = i, a[id].d = d;
}
}
for (auto [id, d, idx] : borp[i]) {
if(stack.size() == 1||a[id].book || i - a[id].res < a[id].d) {
}
else {
a[id].book = stack.back();
stack.pop_back();
}
a[id].res = a[id].d = 0;
}
for (auto [id, d, idx] : bor[i]) {
if(stack.size() == 1||a[id].book || i - a[id].res < a[id].d) {
ans[idx] = 0;
}
else {
ans[idx] = stack.back();
a[id].res = a[id].d = 0;
a[id].book = stack.back();
stack.pop_back();
}
}
for (auto [id, d, idx] : ret[i]) {
if(!a[id].book) {
ans[idx] = 0;
}
else {
ans[idx] = a[id].book;
stack.push_back(a[id].book);
a[id].book = 0;
}
}
for (auto [id, d, idx] : que[i]) {
if(!a[id].book) ans[idx] = 0;
else ans[idx] = a[id].book;
}
}
for (int i = 1; i <= m; i ++) {
cout << ans[i] << '\n';
}
}
C
很明显0与任何数与都是0,且又要求之多操作n次,故而可以找到操作一次变成0,然后用0与其他数字与,观测数据范围,可以暴力求解
点击查看代码
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i ++) cin >> a[i];
for (int i = 0; i < n; i ++) {
for (int j = i + 1; j < n; j ++) {
if((a[i] & a[j]) == 0) {
cout << "Yes\n";
return;
}
}
}
cout << "No\n";
}
D
选择一个下标,然后让它的值减少1,最多操作k次,问最多的低谷是多少
尝试贪心,发现很复杂,看数据范围考虑dp
设dp[i][j]代表前i项,最多低谷为j
考虑如何转移,i不是低谷可继承前面,i为低谷,其值要小于两边的值,故而:
dp[i][j] = min(dp[i - 1][j], dp[i - 2][j - 1] + max(0, a[i] - min(a[i - 1], a[i + 1]) + 1))
点击查看代码
void solve() {
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) cin >> a[i];
vector<vector<int>> dp(n + 1, vector<int>(n + 1, 1e18));
dp[0][0] = dp[1][0] = 0;
for (int i = 2; i <= n - 1; i ++) {
for (int j = 0; j <= i; j ++) {
dp[i][j] = min(dp[i - 1][j], dp[i - 2][max(0ll, j - 1)] + max(0ll, a[i] - min(a[i - 1], a[i + 1]) + 1));
}
}
int ans = 0;
for (int i = n; i >= 0; i --) {
if(dp[n - 1][i] <= k) {
ans = i;
break;
}
}
cout << ans << '\n';
}
E
先观察数据,n很小考虑暴力
每天可以选一个技能释放,一共四个技能,一共4^n次,好像不太行
但是又有冷却时间的限制,故而当天最多可供选择的技能只有两个,总复杂度大约在2^n次,复杂度绰绰有余
然后就暴力枚举就行,看了别人提交的代码,发现自己写的有点复杂,就不解释了,可以看看别人提交的代码
点击查看代码
struct node {
int day, sum, d1, d2, d3, d4, ans;
};
void solve() {
int n, a;
cin >> n >> a;
vector<int> d(n);
for (int i = 0; i < n ; i ++) cin >> d[i];
int ans = 0;
queue<node> q;
q.push({0, 0, 0, 0, 0, 0, 0});
while(q.size()) {
auto [day, sum, d1, d2, d3, d4, Ans] = q.front();
q.pop();
if(day == n) {
ans = max(ans, Ans);
continue;
}
int Zero = (d1 == 0) + (d2 == 0) + (d3 == 0) + (d4 == 0);
int One = (d1 == 1) + (d2 == 1) + (d3 == 1) + (d4 == 1);
if(!Zero) continue;
if(One > 1) continue;
if(d1 == 0) q.push({day + 1, sum, 1, d2 ? (d2 + 1) % 3 : 0, d3 ? (d3 + 1) % 3 : 0, d4 ? (d4 + 1) % 3 : 0, Ans + d[day] * (d4 == 1 ? 2 : 1)});
if(d2 == 0) q.push({day + 1, sum + a, d1 ? (d1 + 1) % 3 : 0, 1, d3 ? (d3 + 1) % 3 : 0, d4 ? (d4 + 1) % 3 : 0, Ans});
if(d3 == 0) q.push({day + 1, sum, d1 ? (d1 + 1) % 3 : 0, d2 ? (d2 + 1) % 3 : 0, 1, d4 ? (d4 + 1) % 3 : 0, Ans + sum * (d4 == 1 ? 2 : 1)});
if(d4 == 0) q.push({day + 1, sum, d1 ? (d1 + 1) % 3 : 0, d2 ? (d2 + 1) % 3 : 0, d3 ? (d3 + 1) % 3 : 0, 1, Ans});
}
cout << ans << '\n';
}
F
排序
将b数组排序后发现,要么不走传送门,要么一直走传送门
故而维护每个传送门到终点的距离,然后找最近的传送门即可
点击查看代码
void solve() {
int n, p, k, g;
cin >> n >> p >> k >> g;
vector<int> a(p), b(k);
for (int i = 0; i < p; i ++) cin >> a[i];
for (int i = 0; i < k; i ++) cin >> b[i];
sort(b.begin(), b.end());
vector<int> f(k);
int l = -1, r = k;
for (int i = 0; i < k; i ++) {
if(b[i] <= g) l = i;
if(b[i] >= g) r = min(i, r);
}
if(l != -1) f[l] = g - b[l];
if(r < k) f[r] = b[r] - g;
if(l != -1 && r < k) {
if(f[l] < f[r]) f[r] = f[l] + 1;
if(f[l] > f[r])f[l] = f[r] + 1;
}
for (int i = l - 1; i >= 0; i --)
f[i] = f[i + 1] + 1;
for (int i = r + 1; i < k; i ++) {
f[i] = f[i - 1] + 1;
}
for (int i = 0; i < p; i ++) {
int id = lower_bound(b.begin(), b.end(), a[i]) - b.begin();
int ans = abs(a[i] - g);
if(id > 0) ans = min(ans, a[i] - b[id - 1] + f[id - 1]);
if(id < k) ans = min(ans, b[id] - a[i] + f[id]);
cout << ans << " \n"[i == p - 1];
}
}
G
暴力枚举即可
点击查看代码
void solve() {
int L, R;
cin >> L >> R;
int ans = 0;
for (int i = 2024; i <= R; i += 4) {
if(i >= L && i <= R) ans ++;
}
for (int i = 2022; i <= R; i += 4) {
if(i >= L && i <= R) ans ++;
}
cout << ans << '\n';
}
H
考虑最终影响排名的是做出相同题数的人,所以考虑全排列,板子题
点击查看代码
struct GETC {
int P = 0, MAX_N = 2e5 + 10;
int f[200010] = {1, 1}, g[200010] = {1, 1}, inv[200010] = {1, 1};
void init(int _P) {
P = _P;
for (int i = 2; i < MAX_N; i++) {
f[i] = f[i - 1] * i % P;
g[i] = (P - P / i) * g[P % i] % P;
inv[i] = inv[i - 1] * g[i] % P;
}
}
int getC(int n, int m) {
if (m > n) return 0;
return f[n] * inv[n - m] % P * inv[m] % P;
}
int getA(int n, int m) {
if (m > n) return 0;
return f[n] * inv[n - m] % P;
}
int lucas(int n, int m, int p) {
if (m == 0) return 1;
return lucas(n / p, m / p, p) * getC(n % p, m % p) % p;
}
} Get;
void solve() {
Get.init(mod);
int n;
cin >> n;
vector<int> cnt(26);
for (int i = 0; i < n; i ++) {
string s;
cin >> s;
cnt[s.length() - 1] ++;
}
int ans = 1;
for (int i = 0; i < 26; i ++) {
ans = ans * Get.getA(cnt[i], cnt[i]) % mod;
}
cout << ans << '\n';
}
I
考虑k轮后lzt获胜条件:没有单个出现的数字
很明显左右两边数字一样一定满足
这意味着k轮两边消耗了k个两个一样的数字,且我们不关心他们间的排序,显然是个全排列:
C(n, k) * A(2 * k, 2 * k)
但是很明显有重复的排列:如...1 1 ...其中1 1重复算了两次,故而需要除以2:
C(n, k) * A(2 * k, 2 * k) / (2 ^ k)
剩下的也显然是个全排列,同样去重
But!
测了一发发现样例都没过!
2 1
1 1 2 2
发现两边数字可以不一样,可以先把一边消掉,但我们不知道cxh第一步选哪边,故而先选两个不一样的数字成对的放在两边,这样无论cxh先选哪边,我们都可应对
故而公式加上这一部分即可
神奇的代码
const int N = 2e5 + 10, mod = 1e9 + 7;
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
struct GETC {
int P = 0, MAX_N = 2e6 + 10;
int f[2000010] = {1, 1}, g[2000010] = {1, 1}, inv[2000010] = {1, 1};
int iv2[2000010];
void init(int _P) {
P = _P;
int pw = 2;
for (int i = 2; i < MAX_N; i++) {
f[i] = f[i - 1] * i % P;
pw = pw * 2 % P;
g[i] = (P - P / i) * g[P % i] % P;
inv[i] = inv[i - 1] * g[i] % P;
}
iv2[MAX_N - 1] = qpow(pw, P - 2);
for (int i = MAX_N - 2; i >= 0; i --) {
iv2[i] = iv2[i + 1] * 2 % P;// 乘以2相当于少除了2
}
}
int getC(int n, int m) {
if (m > n) return 0;
return f[n] * inv[n - m] % P * inv[m] % P;
}
int getA(int n, int m) {
if (m > n) return 0;
return f[n] * inv[n - m] % P;
}
int calc(int n) {
if(n < 0) return 0;
return f[n * 2] * iv2[n] % P;
}
int lucas(int n, int m, int p) {
if (m == 0) return 1;
return lucas(n / p, m / p, p) * getC(n % p, m % p) % p;
}
} Get;
void solve() {
int n, k;
cin >> n >> k;
cout << (Get.getC(n, k) * Get.calc(k) % mod * Get.calc(n - k) % mod + Get.getC(n, 2) * 2 % mod * Get.getC(n - 2, k - 1) % mod * Get.calc(k - 1) % mod * Get.calc(n - k - 1) % mod) % mod<< '\n';
}
J
算出圆与直线的交点,然后处理重叠部分即可
点击查看代码
void solve() {
int x0, y0, k;
cin >> x0 >> y0 >> k;
vector<pair<double, double>> a;
for (int i = 0; i < k; i ++) {
int x, y, r;
cin >> x >> y >> r;
if(abs(y - y0) > r) continue;
double sq = sqrtl(r * r - (y - y0) * (y - y0));
double x1 = x - sq, x2 = x + sq;
if(x2 < double(x0)) continue;
x1 = max(x1, (double)x0);
a.push_back({x1, x2});
}
double ans = 0;
double lst = x0, r = x0;
sort(a.begin(), a.end(), [&](auto x, auto y){
return x.first < y.first;
});
for (int i = 0; i < a.size(); i ++) {
if(a[i].first <= r) r = max(r, a[i].second);
else {
ans += r - lst;
lst = a[i].first;
r = a[i].second;
}
}
ans += r - lst;
cc(15) << ans << '\n';
}
K
贪心,发现一定是大的单独放,最后放一堆小的更优(考虑平均值)
点击查看代码
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<int> a(k), p(k);
for (int i = 0; i < k; i ++) cin >> a[i];
iota(p.begin(), p.end(), 0);
sort(p.begin(), p.end(), [&](int x, int y){
return a[x] > a[y];
});
int tot = k, cnt = 0;
for (int i = 0; i < n; i ++) {
int res = 1;
if(cnt < k) cout << p[cnt ++] + 1 << " ";
else cout << 0 << ' ';
tot --;
while((n - i - 1) * m < tot) cout << p[cnt ++] + 1<< " ", res ++, tot --;
while(res < m) cout << 0 << ' ', res ++;
cout << '\n';
}
}
L
贪心,从后往前枚举每个位置是否可以到达n
当前位置需要的次数x, 要删除的数 2 * x
满足 2 * x <= n 即可
点击查看代码
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i ++) cin >> a[i];
int mx = a[n - 1];
for (int i = n - 1; i >= 0; i --) {
if((n - i - 1) * 2 < n) mx = max(mx, a[i]);
else break;
}
cout << mx << '\n';
}
M
回顾D的easy版,n很小我们用dp很轻松的就解决了,此题加强的难度n到了2e5,无法在用dp求解
那么该如何是好?
到此时不得不在拾回贪心的想法:每次让最小代价先做
如何保证其正确性?
先考虑如何实现:
考虑以i为中心的最大山谷的最小次数,实现如下:
为了使山谷最大,往外扩展时,最高点应该是最外边(i旁边的旁边)的两个,因此i要上升,两边下沉
这样做一定是最多的
我们再用优先队列维护最小值,那么一定又是最小的,故而正确性显而易见
神奇的代码
void solve() {
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
vector<int> pre(n + 2), nxt(n + 2), b(n + 1);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> q;
for (int i = 2; i < n; i ++) {
b[i] = max(0ll, a[i] - min(a[i - 1], a[i + 1]) + 1);
pre[i] = i - 1;
nxt[i] = i + 1;
q.push({b[i], i});
}
b[1] = b[n] = 1e9;
vector<int> vis(n + 1);
auto del = [&](int i) {
vis[pre[i]] = vis[nxt[i]] = 1;//此次两边已经是谷底,无需再遍历以他为谷底时的状况
pre[i] = pre[pre[i]];
nxt[i] = nxt[nxt[i]];
nxt[pre[i]] = i;
pre[nxt[i]] = i;
};
int ans = 0;
while(q.size()) {
auto [w, i] = q.top();
q.pop();
if(vis[i]) continue;
if(k < w) break;
k -= w;
ans ++;
b[i] = b[pre[i]] + b[nxt[i]] - b[i];
q.push({b[i], i});
del(i);
}
cout << ans << '\n';
}