2023牛客暑期多校训练营4

H - Merge the squares!

https://ac.nowcoder.com/acm/contest/57358/H

题意

给定 n×n1×1 组成的正方形,每次可以合并相邻不超过 50 个正方形变成一个大正方形。问如何通过合并得到一个大的 n×n 的大正方形,不限次数。 1n103

题解

这道题是一道构造题,重点是找到一个通用的构造方法。

对于一个边长为 c 的正方形,可以很简单地分成两个小正方形和两个小长方形,小正方形的边长为 a,b,面积关系满足:

c2=(a+b)2=a2+b2+2ab

对于分出来的正方形,可以一直递归下去解决。先假定递归能够成功回来,那么边长为 a,b 的小正方形就已经合好了,在这个区域内只占 2 个合并名额。一次性最多合并的数量是 50 个,那么分给旁边两个小长方形的合并名额便是每个 24 个。

接下来的问题就是,如何判定能在 24 个名额中合并好两个小长方形。这个可以用辗转相除法(欧几里得算法)解决:

先用宽做边长,切出几个正方形,再用剩下的长方形的宽做边长,切出几个正方形,一直这样下去直到全部切成正方形,最后把数目统计出来即可。如果切出的数目 >24,则说明不成立,如果切出的数目 24,则说明成立。

那对于一个边长为 n 的大正方形,有多种切法,那该选择哪一种呢。由于欧几里得算法的时间复杂度为:O(a+b)a,b 为输入的两个数,那么可以选择预处理遍历所有的切割情况,记录下合法的切法。这个时间复杂度 O(n2) 可以接受。

预处理好后就可以开始正式计算了,使用递归分治的思想。对于边长为 n 的正方形:

  • 如果 n=1:结束算法
  • 如果 n>1:在预处理的表中查询到正确切法 a,b

    • 如果查到可以直接合并

      • 储存合并方式后直接结束
    • 如果查到需要切割

      • 储存合并方式
      • 递归处理左上 a×a 正方形
      • 递归处理右下 b×b 正方形
      • 递归处理左下 b×a 长方形
      • 递归处理右上 a×b 长方形

对于边长 a,b 的长方形(a>b):

  • 如果边长为 0:结束算法
  • 如果边长都不为 0

    • 从切出的第 1 个正方形遍历到第 a/b 个正方形:

      • 递归处理每一个正方形
    • 递归处理剩下的长方形 b,amodb

代码

#include <bits/stdc++.h>
#define endl '\n'
// #define int long long

using namespace std;

constexpr int MAXN = 1010;
int split[MAXN];
vector<array<int, 3>> res;

// a-lu b-rd
bool check_split(int a, int b)
{
    if (!b)
        return a <= 7;
    int ans = 0;
    while (b)
    {
        ans += a / b;
        a %= b;
        swap(a, b);
    }
    return ans <= 24;
}

void init_split(int n)
{
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= i / 2; j++)
        {
            // i - j >= j
            if (check_split(i - j, j))
            {
                split[i] = j;
                break;
            }
        }
    }
    // for (int i = 1; i <= n; i++)
    //     cout << split[i] << " \n"[i == n];
}

void dfs(int x, int y, int a);
void dfs_hori(int x, int y, int r, int c);
void dfs_vert(int x, int y, int r, int c);

void dfs_hori(int x, int y, int r, int c)
{
    if (r < 1 || c < 1)
        return;
    int cnt = c / r;
    for (int i = 0; i < cnt; i++)
        dfs(x, y + i * r, r);
    dfs_vert(x, y + cnt * r, r, c % r);
}

void dfs_vert(int x, int y, int r, int c)
{
    if (r < 1 || c < 1)
        return;
    int cnt = r / c;
    for (int i = 0; i < cnt; i++)
        dfs(x + i * c, y, c);
    dfs_hori(x + cnt * c, y, r % c, c);
}

void dfs(int x, int y, int a)
{
    if (a == 1)
        return;
    res.push_back({x, y, a});
    if (split[a] == 0)
        return;
    int lu = a - split[a], rd = split[a];
    dfs(x, y, lu);
    dfs(x + lu, y + lu, rd);
    dfs_hori(x + lu, y, rd, lu);
    dfs_vert(x, y + lu, lu, rd);
}

void solve()
{
    int n;
    cin >> n;
    init_split(n);
    dfs(1, 1, n);
    cout << res.size() << endl;
    for (int i = res.size() - 1; i >= 0; i--)
        cout << res[i][0] << ' ' << res[i][1] << ' ' << res[i][2] << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    // cin >> t;
    while (t--)
        solve();
    return 0;
}