题目
给定n(n<=4e5)个数,第i个数ai(1<=ai<=1e9),
你可以用其中的若干个数,构成一个美丽矩阵,
美丽矩阵即同一行内不存在相同的数,同一列内不存在相同的数的矩阵
要求最大化美丽矩阵的面积,并输出面积、长、宽和构造的矩阵
思路来源
自己乱搞2h,wa6发(×
题解
注意到一个事实是,如果想在一个矩阵里放x个相同的值,按对角线放最优,即长宽>=x都得成立,
于是,不妨设行<=列,并且二分最大的行row,答案只可能是<=row的行
注意到行答案不一定是row,
比如,
14
1 1 1 2 2 2 3 3 3 4 4 4 5 5
此时,最大行数是3是可以的,但是放不下3*5,于是2*7比3*4更优
所以考虑枚举每一个行i<=row的行i,对每个出现次数与i取小,更新最大面积值,
考虑等于矩形最大面积时如何构造答案,
第一想法是沿着第一行往右下扫,扫到底了就往右挪一个位置继续往右下扫,如下
123
312
231
但是,如果这么盲目构造的话,考虑以下case,
16
2 2 3 3 3 3 4 4 4 5 5 5 6 6 6 7 7 7
16
4 4
2 3 4 6
6 2 3 5
5 6 3 4
4 5 7 3
容易发现,存在两个位置重复在同一列/同一行
即,如果从第一行往右下搞对角线平行线的话,是可以等于4个(row)的,
如果不是同一条线的话,只能等于3个(row-1),
所以,按照各个值的出现次数排个降序,再按这种方法放就可以了
代码
#include<iostream>
#include<cstdio>
#include<map>
#include<vector>
#include<algorithm>
#include<cstring>
#include<assert.h>
using namespace std;
const int N=4e5+10;
typedef long long ll;
ll n,a[N],now[N],to[N],c,mn[N],sum;
ll cnt[N],ans,col,row,x,y,res[N],val[N],e,id[N];
bool cmp(int x,int y){
return mn[x]>mn[y];
}
int f(int x,int y){
return x*col+y;
}
int main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
for(int i=1;i<=n;++i){
if(i==1 || a[i]!=a[i-1])now[++c]=1,to[c]=a[i];
else now[c]++;
}
ll l=1,r=n;
while(l<=r){
ll mid=(l+r)/2;
sum=0;
for(int i=1;i<=c;++i){
mn[i]=min(now[i],mid);
sum+=mn[i];
}
if(sum<1ll*mid*mid){
r=mid-1;
}
else{
l=mid+1;
}
}
sum=0;
for(int i=1;i<=c;++i){
mn[i]=min(now[i],r);
cnt[mn[i]]++;
sum+=mn[i];
id[i]=i;
}
for(int i=r;i>=1;--i){
cnt[i]+=cnt[i+1];
sum-=cnt[i+1];
if(ans<sum/i*i){
ans=sum/i*i;
row=i,col=sum/i;
}
}
for(int i=1;i<=c;++i){
mn[i]=min(now[i],row);
}
sort(id+1,id+c+1,cmp);
for(int i=1;i<=c;++i){
int v=id[i];
for(int j=0;j<mn[v];++j){
val[e++]=to[v];
}
}
assert(e>=col*row);
for(int i=0;i<col*row;++i){
res[f(x,y)]=val[i];
if(x==row-1)x=0,y=(i+1)/row;
else x=(x+1)%row,y=(y+1)%col;
}
printf("%lld\n",ans);
printf("%lld %lld\n",row,col);
for(int i=0;i<row;++i){
for(int j=0;j<col;++j){
printf("%lld%c",res[f(i,j)]," \n"[j==col-1]);
}
}
return 0;
}