抽屉原理(鸽巢原理)
作者:互联网
已知n+ 1个正整数,它们全都小于或等于2n,证明当中一定有两个数是互质的
取n个盒子,在第一个盒子我们放1和2,在第二个盒子我们放3和4,第三个盒子是放5和6,依此类推直到第n个盒子放2n-1和2n这两个数。
如果我们在n个盒子里随意抽出n+1个数。我们马上看到一定有一个盒子是被抽空的。
因此在这n+1个数中必有两个数是连续数,很明显的连续数是互质的。因此这问题就解决了!这就是利用了鸽巢原理的核心思想。
从n个数中选出几个数和为n的倍数
这个构造很牛 之前算莫队的时候也有用到这个统计方案 但是没想到会有抽屉原理在里面
可以保证连续的数一定存在解 当然选的数可以是不连续的 但是其他的方案在有限的时间内是无法算出来的
题意 :给定一个n行m列的矩阵,你最多可以从里面拿出 (m-1)行 ,使得 拿出来的这个矩阵 每列的最大值中的最小值最大。范围 n×m <= 1e5
首先,很明显,选择的行数越多越好(因为是先取max,所以更多的行数不会使得答案变差)
这个题的难点就在于行和列互相联系转换
m列 所以每列的最大值 所在行 可能有m个不同行
要求我们选择m-1行 所以必定至少有两列的最大值位于同一行 (抽屉原理 这个点位是这道题的关键)
综上,如果 n <= m-1,由于选的越多越好,把所有行都选了就是最佳答案。
如果n>=m呢,注意题目中的 n×m <=1e5
两边乘上m , m×m<= n×m <= 1e5
那么我们就可以枚举是哪两个列的最大值的位置是同一行。复杂度 大概就是n*sqrt(n)的复杂度。
当然也可以二分
void slove() {
cin >> n >> m;
vector<vector<int>>a;
a.resize(n + 1);
vector<int>mx;
mx.resize(m + 1);
//mx保存每一列的最大值
for (int i = 0; i < a.size(); i++)a[i].resize(m + 1);
for (int i = 1; i <= n; i++)for (int j = 1; j <= m; j++)cin >> a[i][j];
for (int j = 1; j <= m; j++) {
mx[j] = 0;
for (int i = 1; i <= n; i++)mx[j] = max(mx[j], a[i][j]);
}
if (n <= (m - 1)) {
int ans = INF;
for (int i = 1; i <= m; i++)ans = min(ans, mx[i]);
cout << ans << endl;
return;
}
int ans = 0;
for (int i = 1; i <= m; i++) {
for (int j = i + 1; j <= m; j++) {
int cur = INF;
//枚举 i ,j
for (int k = 1; k <= m; k++)
if (!(k == i || k == j))cur = min(cur, mx[k]);
//cur 除了i j 列的 最大值中的最小
int res = 0;
for (int k = 1; k <= n; k++)
res = max(res, min(a[k][i], a[k][j]));
// 枚举每一行,是的 i ,j 列的最小值 最大
ans = max(ans, min(cur, res));
}
}
cout << ans << endl;
}
二分:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
vector<int> sp[maxn + 10];
int n, m;
bool check(int x)
{
vector<bool> f(n + 10);
bool ff = false;
for (int i = 1; i <= m; i++) {
int cnt = 0;
for (int j = 1; j <= n; j++) {
if (sp[i][j] >= x) {
cnt++;
f[j] = 1;
}
}
if (cnt >= 2)
ff = true;
}
if (!ff)
return false;
for (int i = 1; i <= n; i++) {
if (!f[i])
return false;
}
return true;
}
void solve()
{
cin >> m >> n;
int mx = 0;
for (int i = 1; i <= m; i++) {
sp[i].clear();
sp[i].emplace_back(0);
for (int j = 1; j <= n; j++) {
int t;
cin >> t;
sp[i].emplace_back(t);
mx = max(t, mx);
}
}
int l = 0, r = mx;
int ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
给定一个长度为n的序列,你可以每次选择一个数,给它加1,可以重复对一个数使用。
对于i in [0,n],是否可以使得序列的mex为i,如果可以最小需要操作多少次。不能输出-1.
很明显,对于某个i,如果原序列中的小于 i 的数的个数小于 i ,那么mex值一定不能为 i 了。
而且,后面的mex都是-1了。
解决了是否可以的问题,来思考下最小操作次数,其实也很简单,这里举个例子。
0 1 1 2 3
如果现在i 为 4 ,当前是可以的。
如果i为5,我们需要把一个1变成4 而且这个1不是随便找的空闲数 而是相差i最小的一个 这样才能保证当前操作数最小
又因为要先满足mex为1才能满足mex为2为3 所以直接从小到大处理即可
void slove() {
cin >> n; for (int i = 0; i <= n; i++)cot[i] = 0;
for (int i = 1; i <= n; i++) {
int x; cin >> x;
cot[x]++;
}
int pre = 0;//小于i的个数
int cur = 0;//使得 0到 i-1 上的每个位置都有数的最少操作数
vector<int>v;//储存多余的数
vector<int>ans;
for (int i = 0; i <= n; i++) {
if (pre < i) {
//-1的情况
ans.push_back(-1);
while (ans.size() < (n + 1))ans.push_back(-1);
break;
}
else {
ans.push_back(cur + cot[i]);
//如果想要mex等于i,如果0 到 i-1上每个位置上都有数
//且 i 上没有数
if (cot[i] == 0 && v.size()) {
cur += i - v.back();
v.pop_back();
//如果i这个位置上没有数的话,需要从前面拿一个数过来 因为插入的时候是按照从小到大插入的
//所以每次直接取空闲数最后一个即可
}
else {
for (int j = 1; j <= cot[i] - 1; j++)v.push_back(i);
//i这个位置只需要一个数 ,cot[i] - 1 的数是多余的
}
}
pre += cot[i];
}
for (int x : ans)cout << x << " ";
cout << endl;
}
标签:盒子,int,最大值,个数,mex,鸽巢,原理,抽屉,mx 来源: https://www.cnblogs.com/wzxbeliever/p/16670456.html