中国地质大学(武汉)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';
}
posted @ 2024-11-04 21:48  不o凡  阅读(32)  评论(0)    收藏  举报