题目大意
我们称一个矩阵是美丽的,当且仅当该这矩阵中不存在两个相同的数在同一列或在同一行。
给定 \(n\) 个数,要求你选出尽量多的数,使它们能够组成一个美丽的矩形。
注意,本题要求输出选出的数的个数与组成矩形大小和具体方案。
\(n\leq 4 \times 10^5\)
题解
想了好几天了,终于想出来了,果然是我太菜...
首先,可以想到,对于一个\(x\)行的矩形(令列数大于等于行数),如果有一个数出现了超过\(x\)次,是不可能将这个数全部填入这个矩形的,最多只能填\(x\)个,即斜着填,按下图的顺序。
那么我们用一个map维护每个数出现的次数,按出现的次数从大到小来填数,相同的数填在一起。为什么要按出现的次数从大到小来填呢?如果随便填的话,会被这组数据hack掉。
7
8 5 10 4 10 8 3
我的答案是
6
2 3
3 5 8
10 4 8
正确答案是
6
2 3
8 10 3
4 8 10
发现虽然是斜着填的,但两个8竟然在同一列。可以证明,按出现的次数从大到小来填数,就不会出现这样的情况。
然后我们就要先确定这个矩形有多少行,我们从\(1 \sim \sqrt n\)去枚举行数,假设当前枚举到的行数为\(Row\),那么对于每个数,我们最多取\(Row\)次,可以用前缀和+二分计算出我们总共能取的数的数量\(Cnt\),然后就可以计算出行数\(Col=\left\lfloor\frac{Cnt}{Row}\right\rfloor\times Row\),但是\(Col\)不能小于\(Row\),不然就不符合我们之前规定的列数大于等于行数。可以证明一定存在一种填法,能够填满这\(Row \times Col\)的矩形。
所以我们可以先把所有数按出现的次数排序,再用\(O\left(\sqrt nlogn\right)\)的时间枚举行数并计算出最大的矩形的大小,接着再用\(O(n)\)的时间去填数,最终的时间复杂度为\(O( nlogn + \sqrt nlogn +n)\)
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
#define RG register int
#define LL long long
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
struct Node{int Num,Cnt;};
map<int,int> Hash;
vector<Node> Com;
vector<int> Data;
vector<int> Mat[1000];
int Sum[400010];
int N,Row=0,Col=0,Total=0;
bool cmp(Node A,Node B){return A.Cnt>B.Cnt;}
inline int Judge(int row){
int Pos=upper_bound(Data.begin(),Data.end(),row)-Data.begin()-1;
int Res=(Sum[Pos]+((int)Data.size()-Pos-1)*row)/row*row;
if(Res/row<row) return 0;
return Res;
}
int CountY=0;
inline void Move(int &px,int &py){
if(px==Row-1) ++CountY;
if(px+1<Row && py+1<Col){++px;++py;return;}
if(px+1>=Row && py+1<Col){px=0;py=CountY;return;}
if(px+1<Row && py+1>=Col){py=0;++px;return;}
if(px+1>=Row && py+1>=Col){px=0;py=CountY;return;}
return;
}
inline void Maintain(){
int px=0,py=0;
for(RG i=1;i<=Col;++i)
for(RG j=0;j<Row;++j)
Mat[j].push_back(0);
for(int i=0;i<(int)Com.size();++i){
int Num=Com[i].Num,Cnt=min(Com[i].Cnt,Row);
for(RG i=1;i<=Cnt;++i){
Mat[px][py]=Num;
Move(px,py);
}
}
for(RG i=0;i<Row;++i){
for(RG j=0;j<Col;++j){
printf("%d",Mat[i][j]);
if(j<Col-1) printf(" ");
}
printf("\n");
}
return;
}
int main(){
Read(N);
for(RG i=1;i<=N;++i){
int x;Read(x);
++Hash[x];
}
Data.push_back(-2147483647);
for(auto it:Hash){
Data.push_back(it.second);
Com.push_back((Node){it.first,it.second});
}
sort(Com.begin(),Com.end(),cmp);
sort(Data.begin(),Data.end());
for(RG i=1;i<(int)Data.size();++i)
Sum[i]=Sum[i-1]+Data[i];
for(RG i=1;i*i<=N;++i){
int temp=Judge(i);
if(temp>Total){Total=temp;Row=i;}
}
Col=Total/Row;
printf("%d\n%d %d\n",Total,Row,Col);
Maintain();
return 0;
}