一、大纲
本周作业与实验题目如下:
- 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老师 每分钟可以选择一个白色的格子并且把它涂黑。现在他想知道要完成这个工作,最少需要几分钟?
- 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, nm 的总和不超过 4 * 10^5
- 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 的数值大小。
- Input
输出文件包含多组测试用例,每组测试用例格式如下:
第一行给定两个正整数 k m。(k < 2e9, m < 1e5)
第二行给定十个整数,分别表示 a0~a9。
- 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;
}