数字游戏 (number)

一道十分毒瘤的模拟题。代码很短,推我花了二三十分钟。

数字游戏

传送门 - 数字游戏

Description

不高兴找到了一个 N N N M M M 列的矩阵。

矩阵的第一行数字为 1 1 1 2 2 2 3 3 3 ,…, M M M

第二行数字为 M + 1 M+1 M+1 M + 2 M+2 M+2 ,…, 2 ∗ M 2*M 2M

N N N 行数字为 ( N − 1 ) ∗ M + 1 (N-1)*M+1 (N1)M+1 ( N − 1 ) ∗ M + 2 (N-1)*M+2 (N1)M+2 ,…, N ∗ M N*M NM

例如,对于 N = 3 N = 3 N=3 M = 4 M = 4 M=4

1 2 3 4

5 6 7 8

9 10 11 12

当然这样的矩阵是无趣的,所以他选择了其中一行或一列,将其乘以非负数,会进行 K K K 次。最后他想知道矩阵中所有值的总和。由于总和可能会很大,需要总和模 1 0 9 + 7 10^9+7 109+7


Input

第一行三个正整数 N N N M M M K K K

以下 K K K 行中的每一行有两种描述:

  • " R R R X X X Y Y Y ", R R R 表示行,即将第 X X X ( 1 <= X <= N )行的所有数乘以 Y Y Y

  • " S S S X X X Y Y Y ", S S S表示列,即将第 X X X (1 <= X X X <= M M M )列的所有数乘以 Y Y Y

Sample Input

3 4 4
R 2 4
S 4 1
R 3 2
R 2 0

Output

输出矩阵所有数的和,并模 1 0 9 + 7 10^9+7 109+7

Sample Output

94

思路

一 概述

首先,对于每一行的整行和小列,我们分开处理,先列后行,且行和列的处理先后顺序对答案不产生影响。

然后,我们就可以用第一行来推出下面的所有行,所有数的和。

注意,以上两种思路是并行的,这也是为什么这道题如此恶心

二 求第一行

对于第一行,就要处理我们刚刚分析到的,小列和小行。

首先,我们建立 2 个变量—— pre 和 sum 。

作用待会再说,先说说原因。

我们的目的,是用一行推出它的下一行。又因为它们是连续的,所以,我们可以得出 这一行的和 + 两行间的差 = 下一行的和。

而,所有行的和就是我们要求的。

则该式成立,我们接下来要求的,就是“这一行的和”以及“两行间的差”。回归我们刚刚建的变量, pre 代表的就是两行间的差 (请忽略这奇奇怪怪的变量名), sum 代表的就是这一行的和。


  • 求 pre :

用样例给的数:( n = 3 , m = 4 )

1 2 3 4

5 6 7 8

在没有进行操作的时候,第 2 行的第 i 个数 = 第 1 行的第 i 个数 + m 。这个很好理解。

那当我给第 1 列乘上 2 呢?该怎么求?

第 1 列就会变成:

2 ——> 10 。

很明显,两者的差就变成了 m × 2 m \times 2 m×2 。那么当我们把每一列(也就是一整行)的差都加起来就会变成 ( l z m [ 1 ] + l z m [ 2 ] + . . . + l z m [ m ] ) ∗ m (lzm[1]+lzm[2]+...+lzm[m])*m (lzm[1]+lzm[2]+...+lzm[m])m ,用了提取公因数。

我们不急于乘以 m ,那么 p r e = l z m [ 1 ] + l z m [ 2 ] + . . . + l z m [ m ] pre=lzm[1]+lzm[2]+...+lzm[m] pre=lzm[1]+lzm[2]+...+lzm[m]

( lzm 是将每一列要乘的数都统计起来了,具体操作看代码。)

  • 求 sum

sum 相对来说就好求多了(我们只需要求第一行的和且不考虑行要乘的数)。

第一行每一小列 i 的数为 i × l z m i i \times lzm_i i×lzmi ,举例理解:

第一行第一列的数为: 1 ;

我们给第一列乘 2 ,则第一行第一列的数就会变为 1 × 2 1 \times 2 1×2

综上,把一整行的求 sum 操作综合起来就是:

s u m = 1 × l z m 1 + 2 × l z m 2 + ⋯ + m × l z m m sum=1 \times lzm_1+2 \times lzm_2+ \cdots +m \times lzm_m sum=1×lzm1+2×lzm2++m×lzmm


回看这两个变量,我们都从 1 统计到了 m ,所以可以用 for 循环统计他们,就变成了:

	int sum, pre;
	sum = pre = 0;
	for (int i = 1; i <= m; i++)
	{
		pre = (pre + lzm[i]) % mod;
		sum = (sum + (i * lzm[i]) % mod) % mod;
	}

lzm 是统计每列要乘的数, lzn 是统计每行要乘的数。

同时,你会发现,我们“先列后行”的思想就体现出来了。

三 推出后面 n − 1 n-1 n1

我们用 ans 统计总和(也就是最终的答案)。

我们现在已经:

  • 统计了每两行之间的差(差值是固定的);

  • 算出了第一行乘完列之后的和(没有成行的数)。

首先对于第一行的 ans 值,易得它为 s u m × l z n 1 sum \times lzn_1 sum×lzn1

而紧接着对于接下来的 2 到 n 行,对于每一行我们都会类比刚才的操作:

  1. 求出 sum : s u m ← s u m + p r e × m sum \gets sum+pre \times m sumsum+pre×m p r e × m pre \times m pre×m 这一步操作我们之前已经说过了);

  2. 求出该行的和并加到 ans 去:对于第 i 行会有 a n s ← a n s + s u m × l z n i ans \gets ans+sum \times lzn_i ansans+sum×lzni (类比第一行的 ans 值)。

就是这两步操作,是从 2 循环到 n ,具体实现就是:

	ans = (ans + (lzn[1] * sum) % mod) % mod;
	for (int i = 2; i <= n; i++)
	{	
		sum = (sum + (pre * m) % mod) % mod;
		ans = (ans + (sum * lzn[i]) % mod) % mod;
	}

这样我们的答案 ans 也就统计出来了。

四 取模

一个小点被我拎出来讲了…

比赛我就是因为取模不正确而 WA 的啊!!!我的 50 pts …

不正确的取模:

	int sum, pre;
	sum = pre = 0;
	for (int i = 1; i <= m; i++)
	{
		pre = (pre + lzm[i]) % mod;
		sum = (sum + i * lzm[i]) % mod;
	}
	ans = (ans + lzn[1] * sum) % mod;
	for (int i = 2; i <= n; i++)
	{	
		sum = (sum + pre * m) % mod;
		ans = (ans + sum * lzn[i]) % mod;
	}

(就拿这两步作实例,前面的不放了。)

每一步都要取模!!只要乘了就要取模!!

所以,正确的取模是:

	int sum, pre;
	sum = pre = 0;
	for (int i = 1; i <= m; i++)
	{
		pre = (pre + lzm[i]) % mod;
		sum = (sum + (i * lzm[i]) % mod) % mod;
	}
	ans = (ans + (lzn[1] * sum) % mod) % mod;
	for (int i = 2; i <= n; i++)
	{	
		sum = (sum + (pre * m) % mod) % mod;
		ans = (ans + (sum * lzn[i]) % mod) % mod;
	}

最后只有 50 行的代码奉上(不用快读再压压行更少):

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int mod = 1e9 + 7;
int n, m, k;
int ans = 0;
const int maxn = 1000005;
int lzn[maxn], lzm[maxn];

inline int read ()
{
	int x = 1, s = 0;
	char ch = getchar ();
	while (ch < '0' or ch > '9'){if (ch == '-') x = -1; ch = getchar ();}
	while (ch >= '0' and ch <= '9'){s = s * 10 + ch - '0'; ch = getchar ();}
	return x * s;
}

signed main ()
{
	n = read (), m = read (), k = read ();
	for (int i = 0; i <= max (n, m); i++) lzn[i] = lzm[i] = 1;
	while (k--)
	{
		char c[5];
		int x, y;
		scanf ("%s", c);
		x = read (), y = read ();
		if (c[0] == 'R') lzn[x] *= y;
		else if (c[0] == 'S') lzm[x] *= y;
		lzn[x] %= mod;
		lzm[x] %= mod;
	}
	int sum, pre;
	sum = pre = 0;
	for (int i = 1; i <= m; i++)
	{
		pre = (pre + lzm[i]) % mod;
		sum = (sum + (i * lzm[i]) % mod) % mod;
	}
	ans = (ans + (lzn[1] * sum) % mod) % mod;
	for (int i = 2; i <= n; i++)
	{	
		sum = (sum + (pre * m) % mod) % mod;
		ans = (ans + (sum * lzn[i]) % mod) % mod;
	}
	printf ("%lld\n", ans % mod);
	return 0;
}

—— E n d End End——

posted @   pldzy  阅读(226)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示