ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

抽屉原理(鸽巢原理)

2022-09-08 18:34:26  阅读:315  来源: 互联网

标签:盒子 int 最大值 个数 mex 鸽巢 原理 抽屉 mx


已知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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有