第六题题目如下:
题目描述
给定一个 n × m (n 行 m 列)的矩阵。
设一个矩阵的价值为其所有数中的最大值和最小值的乘积。求给定矩阵的所有大小为 a × b (a 行 b 列)的子矩阵的价值的和。
答案可能很大,你只需要输出答案对 998244353 取模后的结果。
输入格式
输入的第一行包含四个整数分别表示 n, m, a, b ,相邻整数之间使用一个空格分隔。
接下来 n 行每行包含 m 个整数,相邻整数之间使用一个空格分隔,表示矩阵中的每个数 Ai, j 。
输出格式
输出一行包含一个整数表示答案。
样例输入
复制
2 3 1 2 1 2 3 4 5 6样例输出
58提示
1×2+2×3+4×5+5×6 = 58 。
对于 40% 的评测用例,1 ≤ n, m ≤ 100 ;
对于 70% 的评测用例,1 ≤ n, m ≤ 500 ;
对于所有评测用例,1 ≤ a ≤ n ≤ 1000 1 ≤ b ≤ m ≤ 1000 1 ≤ Ai, j ≤ 109 。
解题思路:
首先求最大值最小值可以用队列来做,但本题是二维的,我们需要把他转换成一维来处理。
怎么转换呢?比如我们要求的子矩阵是3*3我们把每一列的最大值最小值处理好,再去遍历每一行的最大值和最小值,然后我们就会发现子矩阵中的最大值最小值恰好就是我们某一列中计算出来的最大值最小值,由此,这题就可以做了。
下面是代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1100;
int matrix[N][N];
int n, m, a, b;
#define mod 998244353
int r_min[N][N], r_max[N][N];
int q[N];
void set_min(int M[], int B[], int m, int k) {
int hh = 0, tt = -1;
for (int i = 1; i <= m; i++) {
if (hh <= tt && i - k + 1 > q[hh]) hh++;
while (hh <= tt && M[i] <= M[q[tt]]) tt--;
q[++tt] = i;
B[i] = M[q[hh]];
}
}
void set_max(int M[], int B[], int m, int k) {
int hh = 0, tt = -1;
for (int i = 1; i <= m; i++) {
if (hh <= tt && i - k + 1 > q[hh]) hh++;
while (hh <= tt && M[i] >= M[q[tt]]) tt--;
q[++tt] = i;
B[i] = M[q[hh]];
}
}
int main() {
cin >> n >> m >> a >> b;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> matrix[i][j];
ll res = 0;
//预处理每一列中的最大值最小值
for (int i = 1; i <= n; i++) {
set_min(matrix[i], r_min[i], m, b);
set_max(matrix[i], r_max[i], m, b);
}
int A[N], B[N], C[N];
//在对每一行进行处理,维护每一列的同时在维护每一行的滑动窗口
for (int i = b; i <= m; i++) {
for (int j = 1; j <= n; j++) A[j] = r_min[j][i];
set_min(A, B, n, a);
for (int j = 1; j <= n; j++) A[j] = r_max[j][i];
set_max(A, C, n, a);
for (int k = a; k <= n; k++) {
res = (res + (ll)B[k] * C[k]) % mod;
}
}
cout << res << endl;
}
第七题题目如下:
题目描述
给定 a, b,求 1 ≤ x < ab 中有多少个 x 与 ab 互质。由于答案可能很大,你只需要输出答案对 998244353 取模的结果。
输入格式
输入一行包含两个整数分别表示 a, b,用一个空格分隔。
输出格式
输出一行包含一个整数表示答案。
样例输入
复制
2 5样例输出
复制
16提示
对于 30% 的评测用例,ab ≤ 106 ;
对于 70% 的评测用例,a ≤ 106,b ≤ 109 ;
对于所有评测用例,1 ≤ a ≤ 109,1 ≤ b ≤ 1018 。
解题思路:
这题就是一道模板题,本题要先用快速幂计算出具体的数值,因为数据过大,所以要进行取模运算,计算完后在套欧拉函数板子即可。
欧拉函数:在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目。
下面是代码:
/*
欧拉函数:求出的是1-N中与N互质的个数
*/
#include <iostream>
#include <algorithm>
#define mod 998244353
using namespace std;
typedef long long ll;
ll n,m;
ll fasetPow(ll a,ll b){
ll ans = 1;
a %= mod;
while (b) {
if (b & 1) ans = (ans * a) % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
int main() {
cin >>n>>m;
n = fasetPow(n,m);
int res = n;
for(int i=2;i<=n/i;i++)
if (n % i == 0){
//这样写是为了防止整数相除问题
res = res / i * (i - 1);
while (n % i == 0) n /= i;
}
if(n>1)
res = res / n * (n - 1);
cout << res << endl;
return 0;
}
第八题题目如下:
题目描述
给定一个含有 n 个元素的数组 Ai,你可以选择两个不相交的子段。求出这两个子段内的数的异或和的差值的最大值。
输入格式
输入的第一行包含一个整数 n 。
第二行包含 n 个整数 Ai ,相邻整数之间使用一个空格分隔。
输出格式
输出一行包含一个整数表示答案。
样例输入
复制
6 1 2 4 9 2 7样例输出
复制
14提示
两个子段可以分别选 1 和 4,9,2,差值为 15 − 1 = 14 。
对于 40% 的评测用例,n ≤ 5000 ;
对于所有评测用例,2 ≤ n ≤ 2 × 105,0 ≤ Ai ≤ 220 。
解题思路:
本题用trie树来做,异或题+区间都可以用trie来做,因为在某一段中我们找到这一段的最大值或是最小值很好找。
根据题意可知求两个不相交的子段可以用双重循环来做,但很明显这种做法必然过不了,所以我们可以预处理出左边的最大值,右边的最小值,左边的最小值以及右边的最大值,这样就能把双重循环变成3*N了。
还有一个关键问题:两个子段中求的不是全部,而是一小部分,那该怎么处理呢?
也很简单,根据trie树性质,如果之前位已经出现过,便不会再这个节点创建新的节点,所以我们处理的也一定是某一子段的异或和。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e5 + 100, M = 20 * N;
int a[N], son[M][2], idx;
int l_min[N], r_max[N]; //左最小值 右边最大值
int l_max[N], r_min[N]; //左最大值 右最小值
int n, m;
void insert(int x) {
int p = 0;
for (int i = 20; i >= 0; i--) {
int u = x >> i & 1;
if (!son[p][u]) son[p][u] = ++idx;
p = son[p][u];
}
}
int query_max(int x) {
int p = 0;
int res = 0;
for (int i = 20; i >= 0; i--) {
int u = x >> i & 1;
if (son[p][!u]) {
p = son[p][!u];
res = res * 2 + !u;
}
else {
p = son[p][u];
res = res * 2 + u;
}
}
return res;
}
int query_min(int x) {
int p = 0;
int res = 0;
for (int i = 20; i >= 0; i--) {
int u = x >> i & 1;
if (son[p][u]) {
p = son[p][u];
res = res * 2 + u;
}
else {
p = son[p][!u];
res = res * 2 + !u;
}
}
return res;
}
void init() {
idx = 0;
for (int i = 0; i <= M; i++) son[i][0] = son[i][1] = 0;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
l_min[0] = r_min[n + 1] = 2e10 + 100;
//初始化节点,如果不初始化,值不会按我们想要的方向发展
insert(0);
int sum = 0;
//预处理左边的最大值最小值
for (int i = 1; i <= n; i++) {
sum ^= a[i];
int MAX = query_max(sum) ^ sum;
int MIN = query_min(sum) ^ sum;
l_max[i] = max(l_max[i - 1], MAX);
l_min[i] = min(l_min[i - 1], MIN);
insert(sum);
}
//初始化trie树
init();
//继续初始化节点
insert(0);
sum = 0;
//预处理右边的最大值最小值
for (int i = n; i >= 1; i--) {
sum ^= a[i];
int MAX = query_max(sum) ^ sum;
int MIN = query_min(sum) ^ sum;
r_max[i] = max(r_max[i + 1], MAX);
r_min[i] = min(r_min[i + 1], MIN);
insert(sum);
}
int res = 0;
//预处理左右的最大值和最小值后,结果就在某一段中
//第一种情况,左边是最大值,右边是最小值
//第二种情况,左边是最小值,右边是最大值
for (int i = 1; i <= n - 1; i++) {
res = max(res, r_max[i + 1] - l_min[i]);
res = max(res, l_max[i] - r_min[i + 1]);
}
cout << res << endl;
return 0;
}