洛谷题解--记3月24日训练
题目1
P2241 统计方形(数据加强版)
题目背景:1997年普及组第一题
题目描述
有一个 $n \times m$ 方格的棋盘,求其方格包含多少正方形、长方形(不包含正方形)。
输入格式:
一行,两个正整数 $n,m$($n \leq 5000,m \leq 5000$)。
输出格式:
一行,两个正整数,分别表示方格包含多少正方形、长方形(不包含正方形)。
输入输出样例 #1
输入 #1
1 | 2 3 |
输出 #1
1 | 8 10 |
题解
方法1
数学公式求解
思路
首先,我看到这题就回忆起了高中的排列组合,对于一个 $n \cdot m$ 的棋盘,我们可以把它看作n行m列。
选择其中的行列做正方形,那么对于边长为 $k$ 的正方形,对于 $n$ 行有 $n-k+1$ 总选择,对于 $m$ 列有 $m-k+1$ 总选择,所以边长为 $k$ 的正方形有 $(n-k+1) \cdot (m-k+1)$ 个。
那么,求出所有的正方形应该是在一个正方形棋盘中划分,这个正方形棋盘的边长显然是 $min(m,n)$ ,所以正方形的个数就是 $\sum_{i=1}^{min(m,n)}(n-i+1) \cdot (m-i+1)$,不妨记 $K=min(m,n)$ ,则有:
以上就是正方形的总个数 $S_1$ 。
下面我们来求长方形的个数 \( S_{2} \)。我们知道总矩形个数=总长方形个数+总正方形个数,那么总矩形个数不就是 \( C_{n+1}^{2} \cdot C_{m+1}^{2} \) 吗?那么长方形个数就是 \( C_{n+1}^{2} \cdot C_{m+1}^{2}-S_1 \)。
我们将公式整理一下,得到:
因此这题的答案就显然易见了。
代码
1 |
|
方法2
枚举法
思路
我们可以枚举在棋盘上边长为 $i,j$ 的矩形,如果 $i=j$ ,则可以认为是正方形并计入 $S_1$ ,否则认为是长方形并计入 $S_2$ 。
那么这时候求边长为 $i,j$ 的矩形个数,我们可以发现,对于边长为 $i$ 的矩形,有 $n-i+1$ 种选择,对于边长为 $j$ 的矩形,有 $m-j+1$ 种选择,所以边长为 $i,j$ 的矩形有 $(n-i+1) \cdot (m-j+1)$ 个。
因此,我们可以枚举 $i,j$ ,并求出 $S_1,S_2$ 。
代码
1 |
|
实际上这个代码还可以优化,因为 $S_1$ 和 $S_2$ 的计算是重复的,我们可以先求出 $S_1$ ,然后 $S_2$ 就等于总矩形个数减去 $S_1$ 。
1 |
|
这样就很像方法1了,只是我们把 $S_1$ 的求和算出来了,时间复杂度变成了 $O(1)$ ,这里的时间复杂度是 $O(min(n,m))$ 。
题目2
P2089 烤鸡
题目背景: 猪猪 Hanke 得到了一只鸡。
题目描述
猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 $10$ 种配料(芥末、孜然等),每种配料可以放 $1$ 到 $3$ 克,任意烤鸡的美味程度为所有配料质量之和。
现在, Hanke 想要知道,如果给你一个美味程度 $n$ ,请输出这 $10$ 种配料的所有搭配方案。
输入格式:
一个正整数 $n$,表示美味程度。 对于 $100\%$ 的数据,$n \leq 5000$。
输出格式:
第一行,方案总数。
第二行至结束,$10$ 个数,表示每种配料所放的质量,按字典序排列。
如果没有符合要求的方法,就只要在第一行输出一个 $0$。
输入输出样例 #1
输入 #1
1 | 11 |
输出 #1
1 | 10 |
题解
方法1
dfs递归
思路
由于这题显然是按照一定方式的全排列,我想到之前学的dfs
刚好有可以打印全排列的方法,于是我就用dfs
来写。
附上之前学dfs
时的案例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int n=0;
bool st[1005];
int arr[1005];
void dfs(int x)
{
///用dfs递归生成n的连续整数全排列
if(x>n)
{
for(int i=1;i<=n;i++)
printf("%5d",arr[i]);
printf("\n");
return;
}
for(int i=1;i<=n;i++)
{
if(!st[i])
{
st[i]=true;
arr[x]=i;
dfs(x+1);
st[i]=false;
arr[x]=0;
}
}
}
int main()
{
scanf("%d",&n);
dfs(1);
return 0;
}
这里的st[]
数组是用来标记当前位置是否被使用过,arr[]
数组用来存储当前排列。
这里讨论n=3
时
初始: [F, F, F]
选1 → [T, F, F] → 选2 → [T, T, F] → 选3 → [T, T, T] (输出123)
回溯 → [T, T, F] → 选3 → [T, F, T] → 选2 → [T, T, T] (输出132)
回溯 → [F, F, F] (进入下一个分支)
之后的操作同理,这里不在本题的讨论范围内。
那么本题就是打印符合不定方程
于是最直观的方法是枚举所有可能情况,即每个位置填1、2或3,然后检查总和是否等于n
。但由于每个位置有3种选择,总共有 $3^{10} = 59049$ 种可能,直接枚举显然效率太低,需要优化。
那么为了减少不必要的计算,可以采用DFS + 剪枝的方法:
- 递归构建序列:从第1个位置开始,依次尝试填入1、2、3,然后递归处理下一个位置。
- 剪枝条件:
- 剩余位置全填1仍超
n
:如果当前和 + 剩余位置数×1 >n
,说明即使后面全填1也会超出,直接剪枝。 - 剩余位置全填3仍不足
n
:如果当前和 + 剩余位置数×3 <n
,说明即使后面全填3也无法达到n
,直接剪枝。
- 剩余位置全填1仍超
- 终止条件:当填满10个位置时,检查总和是否等于
n
,如果是,则记录该序列。
代码
1 |
|
方法2
枚举