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,a2……an−1,an} ,求 a n s ans ans 可能达到的最大值。
问在最多去 n − 1 n-1 n−1 家店的情况下, 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]={10mid≤s[i][j]mid>s[i][j],(0≤i<m,0≤j<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}}) O(n∗m∗log109)
#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 n−1 行,也就是说最差最差的情况为这 n n n 列的最大值所在的行互不相交(总行数不一定为 n n n,因为每一列的最大值不是唯一的)。
首先我们可以确定,如果选 n − 1 n-1 n−1 个最大值最大的行,也就是将拥有的最大值最小的那一行省去的话,所得的必然不是最优解(可以自行构造样例想下)。意味着,这能够取到 n n n 列分别最大值的这 n n n 行,我们最多能够取 n − 2 n-2 n−2 行,那么最后一行,也就是最关键的一行要怎么找呢?
这里我是赛后看完佬们的代码才懂的,虽然不知道怎么想出来这种贪心的方式,但是我能够尝试着证明这种做法所得到的解必然是最优的。佬的方式是:选择 m m m 行中第二大值最大的那一行,我们先记这个特殊行为第 k k k 行吧。
如果我们选了第 k k k 行,那么自然而然的,剩余的 n − 2 n-2 n−2 行要选择:能够取到 n n n 列最大值的 n n n 行中,除去第 k k k 行最大值与次大值所在列(将这两列记为 x , y x,y x,y )的最大值所要选择的对应两行。
走到这一步,除去 x 、 y x、y x、y 两列,剩余的 n − 2 n-2 n−2 列所取得的必然是最大值,不可能找到更优的情况,因此只需要看 x 、 y x、y x、y 两列。
那么最重要的问题来了,我们为什么要选择第二大值最大的那一行呢?因为我们最多只能保证
n
−
2
n-2
n−2 列最优,但是我们也想尽可能的让
x
、
y
x、y
x、y 两列,也就是说不能去找最大值的那两列所能取的值是尽可能大的;其次,因为确定最终的答案
a
n
s
ans
ans 的是这
n
n
n 列所能取得的最大值的最小值,也就是能够影响最终答案的是
x
、
y
x、y
x、y 两列中取值较小的那一个。既然这样,我们为什么不在所有行中去找第二大值最大的那一行呢?这种操作能够保证在
n
−
2
n-2
n−2 列最优的情况下,剩余两列能对答案造成的影响绝对是所能够做到最好的了。
证毕。
另外,我们不需要故意将 x 、 y 、 k x、y、k x、y、k 的值求出来,也不需要分类讨论,可以证明的是 n n n 列最优与 k k k 行第二大值的最小值一定是所有情况的最优解,由于篇幅问题就不详细解释了,有兴趣可以自己证明下。
时间复杂度: O ( n ∗ m ∗ log n ) O(n*m*\log{n}) O(n∗m∗logn)
#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
*/