SHCPC2024 M 不共戴天

一道【真的是小清新吗】构造题。

题面在这里

首先,题目转化为:我们想构造两种将 \(n\) 个元素划分为 \(s = n - m\) 个子序列的方案。我们令这两个方案的序列集为 \(S\)\(T\)。对于 \(S\) 中任意元素 \(x\)\(T\) 中任意元素 \(y\),均需满足 \(x\)\(y\) 的公共点最多只有一个。我们需要构造出 \(m\) 最大的情况下的两种方案。

不久我们又发现,题目要求下,子序列内部元素的顺序其实是无关紧要的。于是直接把题意转化为划分为集合就行了。

如果我们想让 \(m\) 尽可能大,就是要让划分的集合数 \(s\) 尽可能小,因为 \(n = s + m\)

那对于每一种划分方案,其中最大的子集 \(K\),大小不能超过 \(s\)。因为此时,我们考虑构造另一种方案。如果最大的那个集合大小超过 \(s\) 的话,由抽屉原理:另一个构造方案中,一定会有一个子集和 \(K\) 交集大小大于 \(1\)

换句话说,另一种构造方案中,每个子集最多分走一个 \(K\) 的元素。因为另一个方案中是 \(s\) 个子集,那么 \(|K| \le s\)

考虑满足上述条件时如何构造,举例:

7fa45f4d33ac531cb1a52a6a547626be

即方案二中每个子集分走方案一中一个子集的一个元素,像 1 2 3 1 2 3 1 这样构造。

最小的 \(s\) 可以暴力找到。对于一种确定好的划分方式,对于每个子集,将集合内的点按下标从小到大排序,这就是一条链,上面的所有边就是构造方案。有 corner case 需要判断,不再赘述,详见代码。

#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;

const int N = 1e4 + 7;
int n, col[N], tp;
vector<int> pth[N];

signed main(){
	ios::sync_with_stdio(0), cin.tie(0);
	
	cin >> n;
	if(n <= 2) return cout << "0\n", 0;
	
	int m;
	for(int i = 1; i <= n; i ++){
		if(n / i + (n % i > 0) <= i){
			m = i; break;
		}
	}
	
	int x = n % m, y = n / m, cur = 1, idx = 1;
	
	cout << n - m << "\n";
	
	for(int i = 1; i <= x; i ++){
		for(int j = 1; j <= y; j ++){
			cout << cur << " " << cur + 1 << "\n";
			cur ++;
		}
		cur ++;
	}
	for(int i = x + 1; i <= m; i ++){
		for(int j = 1; j < y; j ++){
			cout << cur << " " << cur + 1 << "\n";
			cur ++;
		}
		cur ++;
	}
	
	cur = idx = 1;
	for(int i = 1; i <= x; i ++){
		for(int j = 1; j <= y + 1; j ++) col[idx ++] = cur;
		cur ++;
	}
	for(int i = x + 1; i <= m; i ++){
		for(int j = 1; j <= y; j ++) col[idx ++] = cur;
		cur ++;
	}
	
	for(int i = 1; i <= n; i ++){
		bool f = false;
		for(int j = 1; j <= tp; j ++){
			if(col[pth[j].back()] < col[i]){
				pth[j].push_back(i), f = 1;
				break;
			}
		}
		if(!f) pth[++ tp].push_back(i);
	}
	
	vector<pair<int, int>> ans;
	for(int i = 1; i <= tp; i ++){
		for(int j = 0; j + 1 < pth[i].size(); j ++){
			ans.push_back({pth[i][j], pth[i][j + 1]});
		}
	}
	
	for(int i = 0; i < n - m; i ++) cout << ans[i].first << " " << ans[i].second << "\n";
	
	return 0;
}
posted @ 2026-07-17 01:39  Trent900  阅读(18)  评论(0)    收藏  举报