2024 ICPC National Invitational Collegiate Programming Contest, Wuhan Site

传送门

I

注意 :题目是要让 \(1\) 都在右边

K

通过手动模拟就可以发现规律, \(fppf\) 循环

B

贪心,位运算

按位枚举最小的最大值,哪一位必须要放1

这体现在如果这位后面全是 \(1\) 的时候,\(n\) 个数的总和开始首次小于目前的 \(sum\) ,就说明 当前位必须放 \(1\)

  • 在枚举当前位的前一位时,发现此时的和 \(>\)\(= sum\) ,那么在前一位放 \(1\) ,结果肯定会大于 \(sum\) ,不符合要求

  • 枚举当前位的时候,此时和 \(< sum\),则在这一位放 \(1\),结果会 \(<\)\(= sum\) ,只要不是 大于,就可以在这一位后面继续放 \(1\) ,让最后的结果为 \(sum\) 。由于枚举每一位都是 必放不可 才会放 \(1\) ,所以这样的最大值是最小的

注意:由于最后数组中的数可能并不是完全相同,所以并不是当前位上的 \(1\) 在每一个数中都存在,所以要看当前的 \(sum\) 最多需要多少个 此位上的 \(1\)

void solve()
{
    int n, sum = 0, u = 0; cin >> n;
    for(int i = 0; i < n; i++) {int q; cin >> q; sum += q;}
    for(int i = 31; i >= 0; i--) {
    	if(((1ll << i) - 1) * n < sum) u += (1ll << i), sum -= (1ll << i) * min(n, sum / (1ll << i));
    }
    cout << u;
}

F

二分

每次从 左下角 \(or\) 右上角 开始判断当前 \(mid\) 是否满足条件

  -- 原因:
  1.左上角开始的话,下面和右边都比当前大,不方便判断个数 
  2.右上角开始的话,左边比它小,下边比它大,方便转移
  3.左下角同理右上角,右下角同理左上角

由于要求 从大到小\(k\) 大的数 -- 转换一下 \(k = n * n - k + 1\) 从小到大第 \(k\) 小的数

以左下角为例 (第 \(k\) 小的数称为 \(mid\))

  • 若当前返回 \(0\),说明比 \(mid\) 小,由于性质,所以这一列从这一行开始都比 \(mid\) 小,\(cnt += i\),转移到右边一列,比当前大的地方继续判断

  • 若当前返回 \(1\),说明不比 \(mid\) 小,那么就转移到这一列的上一行,比它的的地方继续判断

只统计比它小的即可,再用 最后的结果 和 \(k\) 的比较 作为 二分 的 \(check\)

int ask(int x, int y, int z){
    cout << "? " << x << ' ' << y << ' ' << z << '\n';
    cout.flush();
    int ans; cin >> ans;
    return ans;
}

void solve()
{
    int n, k; cin >> n >> k;
    k = n * n - k + 1; //正数的顺序
    auto check = [&](int x) {
    	int cnt = 0, i = n, j = 1;
    	while(i >= 1 && j <= n) {
    		if(ask(i, j, x) == 1) j++, cnt += i;
    		else i--;
    	}
    	return (cnt >= k ? true : false);
    };
    int l = 1, r = n * n;
    while(l <= r) {
    	int mid = l + r >> 1;
    	if(check(mid)) r = mid - 1;
    	else l = mid + 1;
    }
    cout << "! " << l << '\n';
}

D

分成四种情况

\(① 一直向右\)

\(② 一直向左\)

\(③ 先向左再向右 (最优解法只会折返一次)\)

\(④ 先向右再向左\)

上面两种都可以直接用 前缀和 求出

先向左再向右

 可以上一个的状态,也就是 dp2[i - 1][j - 1] 
 假设它先向左移,在此处再判断 是继续左移比较好 还是 开始右移
 而在 i - 1 处的状态在上一轮已经判断过,就可以直接用了

先向右再向左

  同理
  但是要从右边开始,因为要继承右边的状态
void solve()
{
	int n, ans = 0; cin >> n;
	vector<int> a(n + 2);
	vector<vector<int>> dp1(n + 2, vector<int>(2 * n + 10)), dp2(n + 2, vector<int>(2 * n + 10));
	for(int i = 1; i <= n; i++) cin >> a[i], a[i] += a[i - 1];
	for(int i = 1; i <= n; i++) {
		//一直向右 or 先向左再向右
		for(int j = 1; j <= 2 * n; j++) dp1[i][j] = max(dp1[i - 1][j - 1], a[min(n, i + j)] - a[i - 1]);
	}
	for(int i = n; i >= 1; i--) {
		//一直向左 or 先向右再向左
		for(int j = 1; j <= 2 * n; j++) dp2[i][j] = max(dp2[i + 1][j - 1], a[i] - a[max(0ll, i - j - 1)]), dp1[i][j] = max(dp1[i][j], dp2[i][j]);
	}
	for(int i = 1; i <= n; i++) {
		int res = 0;
		for(int j = 1; j <= 2 * n; j++) res ^= dp1[i][j] * j;
		ans ^= (res + i);		
	}
	cout << ans;
}

M

相差为 \(1\) 的两个数 相加 得到一个数,让最后的 字典序最大

就是 奇数 ➕ 偶数 \(=\) 奇数 尽可能大,所以从 最大的偶数 \(x\) 依次 寻找 ,先找 是否存在 \(x + 1\),因为这样 字典序最大;没有则再找是否存在 \(x - 1\);都没有就说明此 \(x\) 不能用

查找

\(① 本就存在\)

\(② 通过其它数经过好几次拼凑而得\)

所以要使用 递归 来判断 \(x + 1\) 或者 \(x - 1\) 是否存在,对一个偶数 \(x\) 进行判断时,在这个过程中 会删去很多用来拼凑的数字 ,所以删除的时候要记录一下,最后 递归到底层还是无法拼凑就要复原 ,再给它加回去

vector<int> res;

void solve()
{
	int n; cin >> n;
	vector<int> a(n), b, ans;
	unordered_map<int, int> mp;
	for(int i = 0; i < n; i++) {
		cin >> a[i], mp[a[i]]++;
		if(a[i] % 2 == 0) b.emplace_back(a[i]);
	}
	auto get = [&](auto&& get, int x) -> int {
		if(mp[x] > 0) {mp[x]--, res.emplace_back(x); return 1;}
		if(x == 1 || x % 2 == 0) return 0; //1 和 偶数 不可能拼凑出来
		return get(get, x / 2) && get(get, x / 2 + 1ll); //继续向下递归,看是否能拼凑出x
	};
	auto check = [&](auto&& check, int x) -> int {
		res.clear(); //清除上次操作的影响,记录此次操作删了什么数字
		if(get(get, x)) return 1;
		//不成立,将此次操作中删的数都加回去
		for(auto& u : res) mp[u]++;
		return 0;
	};
	sort(b.rbegin(), b.rend());
	for(auto& u : b) {
		if(mp[u] <= 0) continue;
		mp[u]--, res.emplace_back(u);
		//要让结果尽可能大,所以要先找 u + 1
		if(check(check, u + 1)) mp[u * 2 + 1ll]++; 
		else if(check(check, u - 1)) mp[u * 2 - 1ll]++;
		else mp[u]++;
	}
	for(auto& [q, w] : mp) {
		for(int i = 0; i < w; i++) ans.emplace_back(q);
	}
	sort(ans.rbegin(), ans.rend());
	cout << ans.size() << '\n';
	for(auto& u : ans) cout << u << ' ';
}
posted @ 2025-08-24 19:04  PeachyGalaxy  阅读(29)  评论(0)    收藏  举报