[CF1521E]Nastia and a Beautiful Matrix
壹、题目描述 ¶
中文翻译:
你有 \(k\) 个数字,他们分别是 \(1,2,3,...,k\),对于数字 \(i\) 你有相同的 \(a_i\) 个。
定义一个 \(n\times n\) 的矩阵为美丽矩阵:
- 这个 \(n\times n\) 的矩阵包含了你所拥有的所有数字;
- 对于每个 \(2\times 2\) 的子矩阵,占用的格子不能超过 \(3\)(当一个格子的数为 \(0\) 时,我们认为它没有被占用)并且每个对角线的数字是不同的;
现在,你要构造一个最小的美丽矩阵。
贰、题解 ¶
最小的美丽矩阵,即我们得找到一个最小的 \(n\),这个 \(n\) 显然具有单调性,我们不妨考虑二分。
对于一个二分的 \(n\),怎么填这个 \(n\times n\) 的矩阵才是最优?
贪心地,我们将奇数列全部填完,把偶数列全部空出来(因为我们可以触及 \(\lceil{n\over 2} \rceil\) 而不是 \(\lfloor{n\over 2} \rfloor\)),这样我们什么限制都不用考虑。
但是每个 \(2\times 2\) 的子矩阵我们都没有填满,空了很多,这很亏。
考虑将刚刚的贪心进行拓展——我们把偶数列的奇数行填了,那么我们总共填了多少格子呢?不难计算,这样填,我们总共可以填 \(n^2-\lceil{n\over 2} \rceil^2\) 个数字,也就是说,只有 \(\sum a_i\le n^2-\lfloor{n\over 2} \rfloor^2\) 时我们才可以装下所有数字。
但是这不一定合法——奇数列的偶数行和偶数列的奇数行不能填同样的数字,所以我们还要考虑出现次数最多的数字 \(x=\{i|a_i=\max\{a\}\}\),对于这个数字,我们只能将它填在不会冲突的位置 —— 所有奇数列,这些奇数列一共有 \(n\times \lceil{n\over 2} \rceil\) 个格子,所以,对于二分的 \(n\),我们有两个限制:
- \(\sum a_i\le n^2-\lfloor{n\over 2} \rfloor^2\);
- \(x=\{i|a_i=\max\{a\}\},a_x\le n\times \lceil{n\over 2} \rceil\);
二分得到 \(n\),然后我们考虑构造方案,考虑这样的一个网格:宅了题解的图
其实这就是上面的 “偶数列的奇数行” 之类的东西,对于蓝色的格子,可以随便乱填的,但是对于红色和黄色,它可能有冲突。同时,同一颜色不会冲突。
我们将所有的 \(a_i\) 从大到小排序,先贪心地,将大的数字尽量往红色填,如果红色填不下,就往蓝色填 —— 可以证明这是一定可行的,因为 红色+蓝色 就有 \(n\times \lceil{n\over 2} \rceil\) 个,这甚至可以满足最大的 \(a_i\).
当红色都被填完时,有几种情况:
- 最大的颜色还没有被填完,那么它会被蓝色消耗掉;
- 最大的颜色已经被填完了,甚至还填了一些其他的颜色;
对于第一种情况,剩下的蓝色格子一定不会和红色冲突,而黄色格子我们可以保证和红色格子填的最大数字不一样 —— 因为 红+蓝 的组合拳已经把最大数字消耗掉,无论如何都不会冲突;
对于第二种情况,可以保证填在红色的比最大数稍微小一点的数字和后面填进黄色的数字不一样 —— 如果蓝色都不能将小的数字吸纳掉,那么它将会变成出现次数最多的数字。
所以,无论如何,这样填都不会冲突。
照着这个思路填格子就好了。
时间复杂度 \(\mathcal O(n\log n)\),在于排序用基排也不是不可以.
叁、参考代码 ¶
const int maxk=1e5;
pll a[maxk+5]; int m, k;
inline void input(){
m=readin(1), k=readin(1);
rep(i, 1, k) a[i]=mp(readin(1), i);
}
inline ll bisearch(){
ll sum=0, maxx=0;
rep(i, 1, k) sum+=a[i].fi, maxx=max(maxx, a[i].fi);
ll l=0, r=1000, mid, ans=-1;
while(l<=r){
mid=(l+r)>>1;
if(sum<=mid*mid-(mid>>1)*(mid>>1) && maxx<=mid*((mid+1)>>1))
ans=mid, r=mid-1;
else l=mid+1;
}
return ans;
}
int mat[1005][1005];
vector<pii>red, blue, yellow;
signed main(){
rep(tmp, 1, readin(1)){
input();
int n=bisearch();
sort(a+1, a+k+1);
red.clear(); blue.clear(); yellow.clear();
rep(i, 1, n) rep(j, 1, n) mat[i][j]=0;
for(int i=1; i<=n; i+=2)
for(int j=2; j<=n; j+=2)
red.push_back(mp(i, j));
for(int i=1; i<=n; i+=2)
for(int j=1; j<=n; j+=2)
blue.push_back(mp(i, j));
for(int i=2; i<=n; i+=2)
for(int j=1; j<=n; j+=2)
yellow.push_back(mp(i, j));
for(int i=k; i>=1; --i) while(a[i].fi){
--a[i].fi;
if(!red.empty()) mat[red.back().fi][red.back().se]=a[i].se, red.pop_back();
else if(!blue.empty()) mat[blue.back().fi][blue.back().se]=a[i].se, blue.pop_back();
else mat[yellow.back().fi][yellow.back().se]=a[i].se, yellow.pop_back();
}
printf("%d\n", n);
rep(i, 1, n){
rep(j, 1, n) printf("%d ", mat[i][j]);
Endl;
}
}
return 0;
}