Codeforces Round #606 (Div. 2) F.Beautiful Rectangle(构造)

题目

给定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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值