CF1775F 题解
前言
这道题我的做法是枚举和动态规划。
这道题对思维能力及模型转化能力有一定要求,且要用到多次动态规划(虽然每次都比较好想),所以是道不错的题。只是可能会有些难想。
感觉其他的大部分题解讲解的真的是清晰得不太明显(因为我很菜),然后我打算针对我思考时所想的时间较长的地方进行较详细的讲解,其他地方略写。
题意
这翻译似乎不那么简洁。
题目大意:在一个无限大的网格图中,告诉你一个大小 $n$,你要选择 $n$ 个格子,使得周长最小。有两种操作:
- 让你输出任意一种方案。
- 让你求方案数。
至于格式见题面的输入输出。
思路
不难发现,所有的格子一定是四联通的,否则周长一定不优于这个。
不难发现,一个凸的图形一定比凹的优。 也就是说,在确定面积的时候周长一定是凸的小。
不难发现,通过 $u=1$ 的情况我们能知道所有的四联通的凸图形一定可以用一个正方形去掉每个角的一部分或者不去掉得到。且完整包含这个图形的最小矩形去掉四个角一定不会有两个角连到一起去(不然这个矩形必定可以再缩小)。以及我们根据小学数学学得 $a+b$ 相同时(即周长相等),$\lvert a-b \rvert$ 越小面积越大,所以 $a$ 与 $b$ 的取值为 $\sqrt n$ 最妥当。
不难发现,我们可以先算去掉一个角的方案数,然后拿一个角的方案数合并四次变成四个角。而对于一个角,既然是凸的,那必定是个阶梯型,不然就凹进去了(既然是阶梯,那么每行去掉的数是具有单调性的,所以发现是个分拆数)。那么可以用这题里面的动态规划方法算出动态规划数组,求个和,及每个角,再用背包的思想把数组合并进一个背包数组,合并四次。
不难发现,做的第一次动态规划,即分拆数的状态为 $g[i][j]$ 表示总和为 $i$、选了 $j$ 个数的方案数,再这里也就是删了 $i$ 个格子,总共删到第 $i$ 行的方案数,转移方程为 $g[i][j] = g[i - 1][j - 1] + g[i - j][j]$,前一项表示新建一个数,初始为 $1$,后一项表示把每项都加 $1$ (这个转移方程式把数从大到小算方案数的)。而第二次是求一个前缀和,表示删去大小为 $i$ 一个角的方案数 $s[i]=\sum_{j=0}^{i}f[i][j]$。至于第三次,是一个背包,状态和一个角唯一的区别在于他不止一个角,第 $i$ 次和并就是第 $i$ 个角,进行四次 $f[i] = f[i - j] \times s[j]$。
不难发现,这道题的思路已经结束了。
代码
#include <bits/stdc++.h>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 1000;
int n, m, g[N + 10][N + 10], s[N + 10], f[N + 10];
void Init(){
g[0][0] = 1;
L(i, 1, N) L(j, 1, i)
g[i][j] = (g[i - 1][j - 1] + g[i - j][j]) % m;
L(i, 0, N) L(j, 0, i) s[i] = (s[i] + g[i][j]) % m;
f[0] = 1;
L(i, 1, 4) R(j, N, 0) L(k, 1, j)
f[j] = (f[j] + 1ll * f[j - k] * s[k] % m) % m;
}
void Solve1(){
scanf("%d", &n);
int a = sqrt(n), b = (n - 1) / a + 1, cnt = 0;
printf("%d %d\n", a, b);
L(i, 1, a){
L(i, 1, b)
putchar(++cnt <= n? '#' : '.');
putchar('\n');
}
}
void Solve2(){
scanf("%d", &n);
int a = sqrt(n), b = (n - 1) / a + 1, c = a + b, ans = 0;
L(i, 1, c - 1)
if(i * (c - i) >= n)
ans = (ans + f[i * (c - i) - n]) % m;
c *= 2, printf("%d %d\n", c, ans);
}
int main(){
int T, u;
scanf("%d%d", &T, &u);
if(u == 1) while(T--) Solve1();
else{
scanf("%d", &m), Init();
while(T--) Solve2();
}
return 0;
}