C/C++百题打卡[6/100]——表格涂色「MCOI-06」Gerrymandering




Chorus ☁️

上一题链接: C/C++百题打卡[5/100]——合唱队形⭐️⭐️ 考查数组.
百题打卡总目录: 🚧 🚧 …


一、题目总述

● 给定正整数 n n n m m m k k k 能否将一个 n × m n\times m n×m 的表格染色,使得每一个颜色形成恰好一个连通块,并且每一个连通块的 “面积” 为 k k k?如果存在,请构造一个合法方案。


输入描述

共二行。

第一行是一个整数 T T T,表示测试数据的次数。

接下来 T T T 行,每行三个正整数 n n n m m m k k k

输出描述

输出 T T T 行。
如果存在,第一行先输出 YES,否则,输出 NO
如果存在,接下来输出 n n n 行,其中每行 m m m 个正整数,其中每一堆 “正整数群” 正好形成一个 “面积大小” 恰好为 k k k 的连通块。

注意

①运行限制——>最大运行时间: 2 s 2s 2s,最大运行内存: 512 M 512M 512M; ②对于 100 100% 100 的数据,保证有 1 ≤ n , m , k , T , n × m ≤ 1 0 6 1\le n,m,k,T,n \times m\le10^{6} 1n,m,k,T,n×m106

● 挑战成功率: 8.64 % 8.64\% 8.64%【很多人评论此题难度不应该是入门,我也觉得,起码是中等难度

在这里插入图片描述

  ● 输入样例

3
3 3 3
3 3 33
6 6 4

  ● 输出样例

YES
1 1 2
1 2 2
3 3 3
NO
YES
1 1 2 2 3 3
1 2 2 4 4 3
1 5 5 4 6 3
5 5 7 4 6 6
8 7 7 7 9 6
8 8 8 9 9 9

样例解释:对于样例一的 “3 3 3” 的一种合法输出示意图如下:

在这里插入图片描述



二、思考空白区






题目难度:⭐️⭐️⭐️

建议思考时间:⌛️ ⌛️ ⌛️








三、题目解析

● 这是一道考查数组的题。主要是别想复杂了,我一开始就想复杂了,做了很久,虽然最后没有通过 “常规方法” 做了出来,但花费了一个小时的时间。而如果想通了的话,只需要10分钟就能做出来。

● 首先,根据基本的数学知识可知,若 n × m ÷ k n \times m÷k n×m÷k 有余数的话,那就说明,无论如何涂色, n × m n\times m n×m 的表格都不能被以 k k k 为面积大小的连通块涂满。

● 其次,当我们的判断结果为 “YES” 后,我们考虑如何打印这个 n × m n \times m n×m 的矩阵呢?

● 关键就是这里,只要我们抓住题目中的一个关键字 —— 它只要求 “面积大小相同” 的连通块,而没要求连通块的形状都相同(当时我就这么做了…)。所以,我们只要用 “蛇形” 涂色即可,示意图如下:

在这里插入图片描述


算法设计

[1]设计 输入模块

[2]设计 蛇形矩阵模块

[3]设计 输出模块


第[1]步:输入模块

#include <stdio.h>
int main()
{
	/* 输入模块 */
	int T, n, m, k;
	cin >> T;
	while (T > 0)
	{
		T--;
		...
		scanf("%d%d%d", &n, &m, &k);
		if (n * m % k != 0)
		{
			printf("NO\n");
		}
		else
		{
			printf("YES\n");
		
			/* 蛇形矩阵模块 */
			...
			
			/* 输出模块 */
			...
			}
		}
	}
	return 0;
}


第[2]步:蛇形矩阵模块

● 设计思路:
  ① 先初始化一个计数器tim = 0,每涂一次就tim++,当涂了 “k” 个方块时,就进行重新赋值为1
  ② 同时初始化一个涂色数字cnt = 1(也就是我们要打印的数字),只有当涂了 “k” 个方块后,执行一次cnt++
  ③ 我们需要将涂色的数字先存储起来(存在a[]中),然后在处理完每一行时,再在最后输出a[]。因为当我们要打印那种 “向右回转再朝左的那种蛇形” 时,我们还并不知道那个 “蛇形” 之前涂色数字(如上图所示的黑色箭头)。
  ④ 所以,我们考虑用一个标志flag来表示,这个 “蛇形”,是向 →,还是向 ←。

#include <stdio.h>
int a[1000010];
int main()
{
	/* 输入模块 */
	int T, n, m, k;
	cin >> T;
	while (T > 0)
	{
		T--;
		/* 每一轮都要初始化 */
		int cnt = 1;
		int tim = 0;
		int flag = 1;	
		/****************/
		scanf("%d%d%d", &n, &m, &k);
		if (n * m % k != 0)
		{
			printf("NO\n");
		}
		else
		{
			printf("YES\n");
			
			/*********** 蛇形矩阵模块 **********/
			for (int i = 1; i <= n; i++)
			{
				if (flag == 1)		// 顺向蛇形处理
				{
					for (int j = 1; j <= m; j++)
					{
						tim++;
						if (tim > k)
						{
							cnt++;
							tim = 1;
						}
						a[j] = cnt;		// 存储 “涂色数字”
					}
					flag = -flag;		// 输完了这一行时, 蛇形准备转向
				}
				else
				{
					for (int j = m; j >= 1; j--)	// 逆向蛇形处理
					{
						tim++;
						if (tim > k)
						{
							cnt++;
							tim = 1;
						}
						a[j] = cnt;
					}
					flag = -flag;		// 输完了这一行时, 蛇形准备转向
				}
				/**************************/
				
				/* 输出模块 */
				...
			}
		}
	}
	return 0;
}


第[3]步:输出模块

● 这个相对简单点,把a[]依次输出即可。注意的是,按题目要求,数组的空间需比 1 0 6 10^6 106 大。

/* 输出模块 */
for (int j = 1; j <= m; j++)
	printf("%d ", a[j]);
printf("\n");


四、做题小结与反思

● 这道题的确,我感觉,做题做多了,一看到就知道用 “蛇形矩阵”。但做得少就可能会耗很多时间。

● 我简单谈谈我之前用的第一种方法(不是蛇形矩阵):
  ① 判断 “YES” 和 “NO” 的机制是一样的。
  ② 然后找出 k k k 的所有配对的 “质因数对 a i × b i a_i\times b_i ai×bi”。
  ③ 然后把这一系列的 “质因数对” 作为除数,被除数是 n n n m m m。找到能除得尽的 “质因数对 a k × b k a_k\times b_k ak×bk”。
  ④ 那个这个以 a_k 和 b_k 作为竖边和横边的 “涂色块” 一定能刚好塞满这个 n × m n \times m n×m 的表格。

比如说, n = 28 , m = 36 , k = 63 n=28,m=36,k=63 n=28m=36k=63
k 的 “质因数对” 有:1和36、3和21、7和9。 其中 n 和 m 分别只能除得尽 7 和 9。
所以这个 涂色块 的竖边长和横边长分别是 7 和 9。 然后按顺序一次塞 涂色块 到表格中即可。



五、完整代码

  ● C 语言版本:

#include <stdio.h>
int a[1000010];
int main()
{
	/* 输入模块 */
	int T, n, m, k;
	scanf("%d", &T);
	while (T > 0)
	{
		T--;
		/* 每一轮都要初始化 */
		int cnt = 1;
		int tim = 0;
		int flag = 1;	
		/****************/
		scanf("%d%d%d", &n, &m, &k);
		if (n * m % k != 0)
		{
			printf("NO\n");
		}
		else
		{
			printf("YES\n");
			
			/*********** 蛇形矩阵模块 **********/
			for (int i = 1; i <= n; i++)
			{
				if (flag == 1)		// 顺向蛇形处理
				{
					for (int j = 1; j <= m; j++)
					{
						tim++;
						if (tim > k)
						{
							cnt++;
							tim = 1;
						}
						a[j] = cnt;		// 存储 “涂色数字”
					}
					flag = -flag;		// 输完了这一行时, 蛇形准备转向
				}
				else
				{
					for (int j = m; j >= 1; j--)	// 逆向蛇形处理
					{
						tim++;
						if (tim > k)
						{
							cnt++;
							tim = 1;
						}
						a[j] = cnt;
					}
					flag = -flag;		// 输完了这一行时, 蛇形准备转向
				}
				/**************************/
				
				/* 输出模块 */
				for (int j = 1; j <= m; j++)
					printf("%d ", a[j]);
				printf("\n");
			}
		}
	}
	return 0;
}

  ● 运行结果:

在这里插入图片描述

  ● C++ 版本:和 C 的差不多,只是输入输出语法不太同。



六、参考附录

[1] 原题地址:https://www.luogu.com.cn/problem/P7886.

上一题链接: C/C++百题打卡[5/100]——合唱队形⭐️⭐️ 考查数组.

百题打卡总目录: 🚧 🚧 …


C/C++百题打卡[6/100]——表格涂色[题目源自 洛谷 ] ⭐️ ⭐️ ⭐️
标签:数组、洛谷月赛、Special Judge、2021

一周一更     
   2021/12/27     

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一支王同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值