Codeforces Round #762 (Div. 3) D. New Year‘s Problem题解(二分&贪心)

D. New Year’s Problem


题意:

n n n 个朋友和 m m m 个商店,每一个商店都有和所有朋友一一对应的 n n n 个礼物,每一个礼物都有一个对应的价值,因此构成了一个 m × n m\times n m×n 矩阵。

你可以在任意一家商店中购买任意数量的礼物,但是每个朋友必须收到一份礼物,并且每个朋友只能收一份礼物。

你的目标是让所有朋友收到的礼物中价值的最小值最大,即 a n s = m i n { a 1 , a 2 … … a n − 1 , a n } ans=min\{a_{1},a_{2}……a_{n-1},a_{n}\} ans=min{a1,a2an1,an} ,求 a n s ans ans 可能达到的最大值。

问在最多去 n − 1 n-1 n1 家店的情况下, a n s ans ans 的最大值为多少?

思路:

这道题很怪,如果二分的话,是可以的,而且思路很清晰;但是群佬们有好多直接贪心搞过的,就很迷,咱赛后试着差不多证了一下,如果有错误的话请多指正。

二分

这个思路是我队友搞出来的,当时我还在和贪心死磕

确实,题目中最大值最小相当于是把二分俩字摁脸上了,那么就是直接二分答案 m i d mid mid,并且答案将原矩阵转化为布尔矩阵: s t [ i ] [ j ] = { 1 m i d ≤ s [ i ] [ j ] 0 m i d > s [ i ] [ j ] , ( 0 ≤ i < m , 0 ≤ j < n ) st[i][j]=\begin{cases}1& {mid\leq s[i][j]}\\0& {mid> s[i][j]}\end{cases},(0\leq i < m,0\leq j < n) st[i][j]={10mids[i][j]mid>s[i][j]0i<m0j<n如果每一列都有 1 1 1 存在,并且至少有一行拥有两个 1 1 1 ,则 m i d mid mid 可行,否则为假。

时间复杂度: O ( n ∗ m ∗ log ⁡ 1 0 9 ) O(n*m*\log{10^{9}}) Onmlog109
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
#define PII pair<int,int>
#define x first
#define y second
#define cf int _; cin>> _; while(_--)
#define sf(x) scanf("%lld",&x)
#define sf2(x,y) scanf("%lld %lld",&x,&y)
#define pft(x) printf("%lld ",x)
#define pfn(x) printf("%lld\n",x)
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define all(x) (x).begin(),(x).end()
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e5 + 10, M = 350, mod = 1e9 + 7;
int m, n ;
bool st[N];

bool check(int mid, vector<vector<int>> &s) {
	bool w1 = false;
	fer(i, 0, m - 1) st[i] = false;

	fer(j, 0, n - 1) {
		bool w2 = false;
		fer(i, 0, m - 1) {
			if (s[i][j] >= mid) {
				if (st[i])
					w1 = true;
				else
					st[i] = true;
				w2 = true;
			}
		}
		if (!w2)
			return 0 ;
	}

	return w1;
}

signed main() {

	cf{
		sf2(m, n);
		vector<vector<int> > s(m, vector<int>(n)) ;
		fer(i, 0, m - 1) fer(j, 0, n - 1) sf(s[i][j]);

		int l = 1, r = 1e9 ;
		while (l < r) {
			int mid = r + l + 1 >> 1 ;
			if (check(mid, s))
				l = mid ;
			else
				r = mid - 1 ;
		}

		pfn(l) ;
	}
	return 0;
}
/*

*/
/*
#                       _oo0oo_
#                      o8888888o
#                      88" . "88
#                      (| -_- |)
#                      0\  =  /0
#                    ___/`---'\___
#                  .' \\|     |// '.
#                 / \\|||  :  |||// \
#                / _||||| -:- |||||- \
#               |   | \\\  -  /// |   |
#               | \_|  ''\---/''  |_/ |
#               \  .-\__  '-'  ___/-. /
#             ___'. .'  /--.--\  `. .'___
#          ."" '<  `.___\_<|>_/___.' >' "".
#         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
#         \  \ `_.   \_ __\ /__ _/   .-` /  /
#     =====`-.____`.___ \_____/___.-`___.-'=====
#                       `=---='
#
#
#     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#               佛祖保佑         永无BUG
*/
贪心

假如,如果题目是最多跑 n n n 个商店的话,这道题会简单不知道多少倍,因为我们只需要找到每一列的最大值所在的行,可以保证所选的行总数是必然 ≤ n \leq n n 的。

但是,题目所给的是最多选 n − 1 n-1 n1 行,也就是说最差最差的情况为这 n n n 列的最大值所在的行互不相交(总行数不一定为 n n n,因为每一列的最大值不是唯一的)。

首先我们可以确定,如果选 n − 1 n-1 n1 个最大值最大的行,也就是将拥有的最大值最小的那一行省去的话,所得的必然不是最优解(可以自行构造样例想下)。意味着,这能够取到 n n n 列分别最大值的这 n n n 行,我们最多能够取 n − 2 n-2 n2 行,那么最后一行,也就是最关键的一行要怎么找呢?

这里我是赛后看完佬们的代码才懂的,虽然不知道怎么想出来这种贪心的方式,但是我能够尝试着证明这种做法所得到的解必然是最优的。佬的方式是:选择 m m m 行中第二大值最大的那一行,我们先记这个特殊行为第 k k k 行吧。

如果我们选了第 k k k 行,那么自然而然的,剩余的 n − 2 n-2 n2 行要选择:能够取到 n n n 列最大值的 n n n 行中,除去第 k k k 行最大值与次大值所在列(将这两列记为 x , y x,y x,y )的最大值所要选择的对应两行。

走到这一步,除去 x 、 y x、y xy 两列,剩余的 n − 2 n-2 n2 列所取得的必然是最大值,不可能找到更优的情况,因此只需要看 x 、 y x、y xy 两列。

那么最重要的问题来了,我们为什么要选择第二大值最大的那一行呢?因为我们最多只能保证 n − 2 n-2 n2 列最优,但是我们也想尽可能的让 x 、 y x、y xy 两列,也就是说不能去找最大值的那两列所能取的值是尽可能大的;其次,因为确定最终的答案 a n s ans ans 的是这 n n n 列所能取得的最大值的最小值,也就是能够影响最终答案的是 x 、 y x、y xy 两列中取值较小的那一个。既然这样,我们为什么不在所有行中去找第二大值最大的那一行呢?这种操作能够保证在 n − 2 n-2 n2 列最优的情况下,剩余两列能对答案造成的影响绝对是所能够做到最好的了。
证毕。

另外,我们不需要故意将 x 、 y 、 k x、y、k xyk 的值求出来,也不需要分类讨论,可以证明的是 n n n 列最优与 k k k 行第二大值的最小值一定是所有情况的最优解,由于篇幅问题就不详细解释了,有兴趣可以自己证明下。

时间复杂度: O ( n ∗ m ∗ log ⁡ n ) O(n*m*\log{n}) Onmlogn
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
#define PII pair<int,int>
#define x first
#define y second
#define cf int _; cin>> _; while(_--)
#define sf(x) scanf("%lld",&x)
#define sf2(x,y) scanf("%lld %lld",&x,&y)
#define pft(x) printf("%lld ",x)
#define pfn(x) printf("%lld\n",x)
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define all(x) (x).begin(),(x).end()
const int inf = 0x3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f ;
const int N = 1e5 + 10, M = 350, mod = 1e9 + 7;
int m, n ;

signed main() {

	cf{
		sf2(m, n);
		vector<vector<int> > s(m, vector<int>(n)) ;
		fer(i, 0, m - 1) fer(j, 0, n - 1) sf(s[i][j]);

		int ans = 0;
		fer(i, 0, m - 1) {
			vector<int> p = s[i];
			sort(all(p), greater<int>());
			ans = max(ans, p[1]);
		}
		fer(i, 0, n - 1) {
			int mx = 0;
			fer(j, 0, m - 1) {
				mx = max(mx, s[j][i]);
			}
			ans = min(ans, mx);
		}
		pfn(ans);
	}
	return 0;
}
/*

*/
/*
#                       _oo0oo_
#                      o8888888o
#                      88" . "88
#                      (| -_- |)
#                      0\  =  /0
#                    ___/`---'\___
#                  .' \\|     |// '.
#                 / \\|||  :  |||// \
#                / _||||| -:- |||||- \
#               |   | \\\  -  /// |   |
#               | \_|  ''\---/''  |_/ |
#               \  .-\__  '-'  ___/-. /
#             ___'. .'  /--.--\  `. .'___
#          ."" '<  `.___\_<|>_/___.' >' "".
#         | | :  `- \`.;`\ _ /`;.`/ - ` : | |
#         \  \ `_.   \_ __\ /__ _/   .-` /  /
#     =====`-.____`.___ \_____/___.-`___.-'=====
#                       `=---='
#
#
#     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
#               佛祖保佑         永无BUG
*/
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值