构造新矩阵
构造新矩阵
给定一个 行 列的整数矩阵,行编号 ,列编号 。
其中,第 行第 列的元素为 。
你可以任意抽取其中不超过 行元素,这些元素之间保持同一行列关系不变,构成一个新矩阵。
构成新矩阵后,我们可以确定一个最大的整数 ,使得新矩阵中每一列都至少存在一个元素不小于 。
我们希望通过合理构造新矩阵,使得 的值尽可能大。
请你计算并输出 的最大可能值。
注意:矩阵一共有 行,但是抽取的行数上限是 行,而不是 行,读题时不要搞混行和列。
输入格式
第一行包含整数 ,表示共有 组测试数据。
每组数据首先包含一个空行。
第二行包含两个整数 。
接下来 行,每行包含 个整数,其中第 行第 个整数表示 $p_{ij}。
输出格式
每组数据输出一行结果,一个整数,表示 的最大可能值。
数据范围
前三个测试点满足 ,。
所有测试点满足 ,,,,一个测试点内所有数据的 值相加不超过 。
输入样例:
5 2 2 1 2 3 4 4 3 1 3 1 3 1 1 1 2 2 1 1 3 2 3 5 3 4 2 5 1 4 2 7 9 8 1 9 6 10 8 2 4 6 5 2 1 7 9 7 2
输出样例:
3 2 4 8 2
解题思路
题目的意思是构造的矩阵的每一列的最大元素都要不小于。首先答案具有二段性,假设的最大取值为,那么很明显是不可能大于,否则就与假设矛盾,而如果存在一种方案,那么当的取值小于该方案也是成立的,因此可以二分答案。
难的地方在于函数,先给出我比赛时的写法。对于二分得到的,先把矩阵中大于等于的值用矩阵标记出来,同时统计每一行标记了多少个元素。然后按照元素数量从大到小排序,从标记元素最多的行开始遍历,同时开个数组用来记录构造矩阵的每一列是否存在大于等于的元素。如果发现某一列还没有被标记(指之前选取的行在该列都没有大于等于的元素),那么我们就选这一行,同时把这行所有能标记的列都标记到数组中对应的列。
很明显这是一种贪心做法,如果发现某一列不存在大于等于的元素,那么很明显我们应该从该列被标记的行这个集合中选,而在这个集合中我们又应该选择标记元素最多的那一行,因为这一行标记的列最多,选择这一行肯定比选择其他标记少的行的效果要好。
最后判断所选的行数是否不超过(这里的指列),且数组是否被全部标记。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10; 5 6 int n, m; 7 vector<int> g[N]; 8 vector<bool> st[N]; 9 int cnt[N], p[N]; 10 bool vis[N]; 11 12 bool check(int mid) { 13 for (int i = 0; i < n; i++) { 14 st[i] = vector<bool>(m); 15 cnt[i] = 0, p[i] = i; 16 for (int j = 0; j < m; j++) { 17 if (g[i][j] >= mid) { 18 cnt[i]++; 19 st[i][j] = true; 20 } 21 } 22 } 23 sort(p, p + n, [&](int i, int j) { 24 return cnt[i] > cnt[j]; 25 }); 26 memset(vis, 0, m); 27 int s = 0; 28 for (int i = 0; i < n; i++) { 29 for (int j = 0; j < m; j++) { 30 if (!vis[j] && st[p[i]][j]) { 31 if (++s >= m) return false; 32 for (int k = 0; k < m; k++) { 33 vis[k] |= st[p[i]][k]; 34 } 35 break; 36 } 37 } 38 } 39 for (int i = 0; i < m; i++) { 40 if (!vis[i]) return false; 41 } 42 return true; 43 } 44 45 void solve() { 46 scanf("%d %d", &n, &m); 47 for (int i = 0; i < n; i++) { 48 g[i].resize(m); 49 for (int j = 0; j < m; j++) { 50 scanf("%d", &g[i][j]); 51 } 52 } 53 int l = 1, r = 1e9; 54 while (l < r) { 55 int mid = l + r + 1 >> 1; 56 if (check(mid)) l = mid; 57 else r = mid - 1; 58 } 59 printf("%d\n", l); 60 } 61 62 int main() { 63 int t; 64 scanf("%d", &t); 65 while (t--) { 66 solve(); 67 } 68 69 return 0; 70 }
再给出y总的做法。
首先如果,那么很明显选择所有的行,的取值就是矩阵中每一列最大值的最小值。
然后是的情况。在函数中,如果发现某一列没有标记元素,即每一行的第列的值都小于,那么无解。然后再考虑每一列都有标记元素的情况,假设题目要求选取行那么肯定是有解的,因为每一列都有标记元素,对应着至多选行就可以覆盖每一列。而如果选行呢?意味着至少存在一行能够覆列,此时这一行覆盖了列,那么剩下的行至少能覆盖列,是一种可行的构造方案。
因此在函数中,遍历每一列,先看看这一列是否存在被标记元素(没有则回传),然后如果枚举到某行发现之前这行之前标记过元素,那么意味着至少存在一行能覆盖两列。对于的情况也可以按照这种方式来判断,因为如果每一列都存在标记元素,那么根据抽屉原理必然至少存在一行标记了两个或以上的元素。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10; 5 6 int n, m; 7 vector<int> g[N]; 8 bool vis[N]; 9 10 bool check(int mid) { 11 memset(vis, 0, n); 12 bool same = false; 13 for (int j = 0; j < m; j++) { 14 bool flag = false; 15 for (int i = 0; i < n; i++) { 16 if (g[i][j] >= mid) { 17 flag = true; 18 if (vis[i]) same = true; 19 vis[i] = true; 20 } 21 } 22 if (!flag) return false; 23 } 24 return same; 25 } 26 27 void solve() { 28 scanf("%d %d", &n, &m); 29 for (int i = 0; i < n; i++) { 30 g[i] = vector<int>(m); 31 for (int j = 0; j < m; j++) { 32 scanf("%d", &g[i][j]); 33 } 34 } 35 int l = 1, r = 1e9; 36 while (l < r) { 37 int mid = l + r + 1 >> 1; 38 if (check(mid)) l = mid; 39 else r = mid - 1; 40 } 41 printf("%d\n", l); 42 } 43 44 int main() { 45 int t; 46 scanf("%d", &t); 47 while (t--) { 48 solve(); 49 } 50 51 return 0; 52 }
参考资料
AcWin 4863. 构造新矩阵(AcWing杯 - 周赛):https://www.acwing.com/video/4628/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17138138.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效