程序设计思维与实践 Week14 Blog

一、大纲

本周作业与实验题目如下:

  • Q老师与十字叉
  • Q老师的考验

二、逐个击破

1.Q老师与十字叉

题目描述

  Q老师 得到一张 n 行 m 列的网格图,上面每一个格子要么是白色的要么是黑色的。
  Q老师认为失去了 十字叉 的网格图莫得灵魂. 一个十字叉可以用一个数对 x 和 y 来表示, 其中 1 ≤ x ≤ n 并且 1 ≤ y ≤ m, 满足在第 x 行中的所有格子以及在第 y 列的 所有格子都是黑色的
  例如下面这5个网格图里都包含十字叉
在这里插入图片描述
  第四个图有四个十字叉,分别在 (1, 3), (1, 5), (3, 3) 和 (3, 5).
  下面的图里没有十字叉
在这里插入图片描述
Q老师 得到了一桶黑颜料,他想为这个网格图注入灵魂。 Q老师 每分钟可以选择一个白色的格子并且把它涂黑。现在他想知道要完成这个工作,最少需要几分钟?

  1. Input

  第一行包含一个整数 q (1 ≤ q ≤ 5 * 10^4) — 表示测试组数
  对于每组数据:
  第一行有两个整数 n 和 m (1 ≤ n, m ≤ 5 * 10^4, n * m ≤ 4 * 10^5) — 表示网格图的行数和列数
  接下来的 n 行中每一行包含 m 个字符 — ‘.’ 表示这个格子是白色的, '’ 表示这个格子是黑色的
  保证 q 组数据中 n 的总和不超过 5 * 10^4, n
m 的总和不超过 4 * 10^5

  1. Output

  答案输出 q 行, 第 i 行包含一个整数 — 表示第 i 组数据的答案

题目分析

  • 这是一道单纯的模拟题。没有复杂的算法。首先是矩阵的输入。在输入的过程中记录每一行黑色格子的个数。这样输入完成之后挑出黑色格子数最多的一行进行填充,答案一定在黑色块最多的行和列。在处理完行之后进行列的处理(因为处理行的过程中将会对列产生影响),过程于上述类似。
  • 空间的粗略计算。假设开常量数组, 5 * 10^4 * 5 * 10^4 = 25 * 10^8 4B -> 即1 * 10^10B。给定内存空间262144KB,即在2e5KB-3e5KB之间,显然不够。如果用动态数组,那么4 * 10^5 * 4B=1.6 * 10^6B,足够。

错误总结
  初始思路的错误在于,黑色格子数最多的行可能有很多,这样填充不同的行对列的影响是不同的,可能使得列需要的填充不能达到最少。使用最朴素的方法。即输入过程中记录每行每列白色格子的值,这样对整个矩阵遍历一遍便可以知道所有十字叉的答案,取其中最小者即可。
  题目的全部代码如下:

#include<iostream>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<vector>
using namespace std;
const int N = 5*1e4 +10;
int n,m,cntx,cnty;
char str[N];
vector<int> v[N];
int row[N],column[N];
 
int main()
{
	int q;
	scanf("%d",&q);
	while(q--)
	{
		memset(row,0,sizeof(row));
		memset(column,0,sizeof(column));
		scanf("%d%d",&n,&m);
		cntx=0,cnty=0;
		for(int i=0;i<n;i++)v[i].clear();
		for(int i=0;i<n;i++)
		{
			scanf("%s",str);
			getchar();
			
			for(int j=0;j<m;j++)
			{
				if(str[j]=='*')
				{
					row[i]++;
					column[j]++;
					v[i].push_back(j);
					cnty= cnty>=column[j]? cnty:column[j];
				}
			}
			cntx = cntx>=row[i]? cntx:row[i];
		}
		
		int ans=n+m-cntx-cnty;
		bool flag1=false;
		for(int i=0;i<n && !flag1;i++)
		{
			if(row[i]!=cntx) continue;//答案一定在黑色块最多的行与列 
			for(int j=0;j<m && !flag1;j++)
			{
				if(column[j]!=cnty) continue;
				bool flag2 = false;
				for(int k=0;k<v[i].size();k++)
				if(v[i][k]==j)
				{
					flag2=1;
					break;
				}
				if(!flag2)
				{
					ans--;
					flag1=1;
				}
			}
		}
		printf("%d\n",ans);		
	}
	return 0;
} 

2.Q老师的考验

题目描述

  Q老师 对数列有一种非同一般的热爱,尤其是优美的斐波那契数列。
  这一天,Q老师 为了增强大家对于斐波那契数列的理解,决定在斐波那契的基础上创建一个新的数列 f(x) 来考一考大家。数列 f(x) 定义如下:
  当 x < 10 时,f(x) = x;
  当 x ≥ 10 时,f(x) = a0 * f(x-1) + a1 * f(x-2) + a2 * f(x-3) + …… + a9 * f(x-10),ai 只能为 0 或 1。
Q老师 将给定 a0~a9,以及两个正整数 k m,询问 f(k) % m 的数值大小。

  1. Input

  输出文件包含多组测试用例,每组测试用例格式如下:
  第一行给定两个正整数 k m。(k < 2e9, m < 1e5)
  第二行给定十个整数,分别表示 a0~a9。

  1. Output

  对于每一组测试用例输出一行,表示 f(k) % m 的数值大小。

题目分析

  题目给定的是线性递推式,如果使用普通方法求每一个f(x)则复杂度是O(10*K),无法接受
  如何优化呢?至少现在我们能想到,f(n)一定能表示成f(0)…f(9)的函数,如果这样不好理解,可以想到我们的高中学过数列,给定数列的初始项,和递推关系,有时候要求其通项公式,如果得到了通项公式,则求f(x)就是O(1)的,那么问题就转换成了如何求f(x)的通项公式
  其实我们可以把数升级到矩阵,每一次递推就相当于一次矩阵运算,多次递推就相当于迭代矩阵运算,具体的,我们可以推导出如下结果:
在这里插入图片描述

  • 计算矩阵多次幂

  借助普通快速幂的思想,我们也可以得到矩阵快速幂,只不过一次相乘的复杂度从O(1)变成了 O ( N 3 ) O(N^3) O(N3),虽然 O ( N 3 ) O(N^3) O(N3)看起来复杂度很高,但主要在于N不会很大,并且logK也不会很大(logK等于乘法的次数)
  具体如何实现:封装矩阵类,重载乘法运算符,动态分配矩阵内存(比静态更灵活),快速幂的单位元变成了单位矩阵

  题目的全部代码如下:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<map>
using namespace std;
int k,m;
struct Matrix
{
    int x[10][10];
    Matrix operator*(const Matrix & t) const{
        Matrix tmp;
        for(int i=0;i<10;i++)
            for(int j=0;j<10;j++)
            {
                tmp.x[i][j]=0;
                for(int k=0;k<10;k++)
                {
                    tmp.x[i][j]+=x[i][k]*t.x[k][j] % m;
                    tmp.x[i][j]%=m;
                }
            }
        return tmp;
    }

    Matrix () {memset(x,0,sizeof(x));}
    Matrix (const Matrix & t) {memcpy(x,t.x,sizeof(x));}
};

Matrix quick_pow(Matrix a,int x)
{
    Matrix tmp;
    for(int i=0;i<10;i++) tmp.x[i][i]=1;
    while(x)
    {
        if(x&1) tmp=tmp*a;
        a=a*a;
        x>>=1;
    }
    return tmp;
}

int main()
{
    while(~scanf("%d%d",&k,&m))
    {
       Matrix mat;
       for(int i=0;i<=9;i++) scanf("%d",&mat.x[0][i]);
       for(int i=1;i<=9;i++) mat.x[i][i-1]=1;
       mat=quick_pow(mat,k-9);
       int now=0;
       for(int i=0;i<10;i++)
            now=(now+mat.x[0][i]*(9-i))%m;
        printf("%d\n",now);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值