第十五节 数论 - 2

AT_agc017_b 题解

洛谷链接&Atcoder 链接

本篇题解为此题较简单做法,请放心阅读。

题目简述

一共有 \(n\) 个格子,给定两个整数 \(A,B\) 分别位于第 \(1\) 和第 \(n\) 格,中间有 \(n−2\) 个空格。询问是否存在一种填数方案满足任意相邻两个数之差的绝对值在 \([C,D]\) 之间。

依次输入 \(n,a,b,c,d\)

若能,输出 YES;反之输出 NO

思路

遇事不决先看数据范围,发现数据范围 \(3 \le N \le 5 \times 10^5\),那么时间复杂度在 \(O(N)\) 以内就可以接受,本篇题解就详细解释一下 \(O(N)\) 的算法。

首先可以想到 \(O(N)\) 的复杂度就是遍历 \(N\),可以想到枚举在 \(A,B\) 之间填了 \(i\) 个数,从 \(0 \sim N-2\) 遍历即可,遍历时进行判断,如果满足要求可直接输出 YES,否则遍历完后输出 NO

接着可以先把区间 \([C,D]\) 转化为区间 \([0,D-C]\),那么判断条件就需要判断 \(C\)合法性及可行性

\[B - (2 \times (i + 1) - N) \times C \]

接着可写出判断条件的边界条件,首先是最大值即右区间,通过右区间 \(D-C\) 很容易得出:

\[A + i \times (D - C) + D \]

以及左区间

\[A + (N - 2 - i) \times (C - D) + C \]

如果合法\(C\) 在此区间内则输出 YES,否则在遍历后输出 NO

注意:因为均为闭区间所以需是 \(\le\) 而不是 \(<\)

经过以上分析及转化,很容易即可得出代码了。

提交记录

\[\text{The End!} \]

AT_arc154_b 题解

洛谷链接&Atcoder 链接

本篇题解为此题较简单做法较少码量,并且码风优良,请放心阅读。

题目简述

给定两个长度为 \(n\) 的字符串 \(S,T\),定义一次操作可取出 \(S\) 的首位,插到 \(S\) 的任意位置。

求最少的操作次数使 \(S\) 变成 \(T\)无解输出 -1

思路

首先很容易想到如果 \(S\)\(T\)某个字母的个数不同,那么 \(S\) 不可能变为 \(T\)因为不可能凭空变出一个字母。通过两个桶数组即可记录 \(S,T\) 的每个字母个数

int cnt_s[30], cnt_t[30];
for(int i = 0; i < n; i ++) 
	cnt_s[s[i] - 'a'] ++, cnt_t[t[i] - 'a'] ++;	

接着验证 \(S\)\(T\) 的各个字母数量是否一致,一旦不一致立刻输出 -1 即可:

for(int i = 0; i < 26; i ++) 
		if(cnt_s[i] != cnt_t[i]) {
			cout << "-1\n";
			return 0;
		}

特判 -1 的情况后就很简单了,仅需遍历 \(T\) 即可,在过程中如果 \(S\) 的当前位与 \(T\) 相同,则表示不必进行操作,把之前记录的 \(ans\) 自减即可

经过以上分析,很容易即可得出完整代码了:

#include<iostream>
using namespace std;

int n, cnt_s[30], cnt_t[30], ans = 0; // 必要数组不解释
string s, t; // 模式串和匹配串

int main() {
	cin >> n >> s >> t;
	ans = n - 1; // 因为下标从 0 开始,所以 ans 需初始化为 n - 1
	for(int i = 0; i < n; i ++) cnt_s[s[i] - 'a'] ++, cnt_t[t[i] - 'a'] ++; // 统计字母个数
	for(int i = 0; i < 26; i ++) 
		if(cnt_s[i] != cnt_t[i]) {
			cout << "-1\n"; // 字母个数不相同直接输出
			return 0;
		}
	for(int i = n - 1; i >= 0; i --)
		if(s[ans] == t[i]) ans --; // 满足条件不必操作,ans --
	cout << ans + 1 << endl; // 还是下标问题答案需 +1,建议写下标从 1 开始的
	return 0;
}

提交记录

\[\text{The End!} \]

AT_arc041_b 题解

洛谷链接&Atcoder 链接

本篇题解为此题较简单做法较少码量,并且码风优良,请放心阅读。

题目简述

给定一个 \(N \times M\) 的矩阵,此矩阵的每一个元素都向上、下、左、右 \(4\) 个方向同时扩散(原来的位置不保留)。

现给出原矩阵扩散后的矩阵,求原矩阵

思路

对于扩散后的 \((i,j)\),是原矩阵\((i-1,j)\)\((i+1,j)\)\((i,j-1)\)\((i,j+1)\) 扩散而来的,那么就可以通过这 \(4\) 个点推断出原矩阵上 \((i,j)\) 的数

例如:

010
101
010

这个样例中,只有 \((2,2)\) 周围 \(4\) 个方向都是非 \(0\),所以从这个例子中就可推出计算原矩阵上 \((i,j)\) 的数的方程式了:

\[(i,j)=\min(\min((i-1,j),(i+1,j),\min((i,j-1),(i,j+1))) \]

那么就会产生一个疑问,对于边界有没有特殊情况?比如对于 \((1,1)\) 他的方程式带入就为:

\[(1,1)=\min(\min((0,1),(2,1)),\min((1,0),(1,2))) \]

所以对于边界 \((0,1)\)\((1,0)\)初始化就尤为重要,这里建议初始化为 \(0\),这样取 \(\min\) 之后就为 \(0\) 了。

处理完边界的初始化之后基本就没什么了,不过我们还是要思考一下,比如对于以下情况:

0000000
0001000
0030400
0209030
0050600
0003000
0000000

\((2,3)\)不是边界并且也不满足四周都是非 \(0\) 数,那么它是不是一种特殊情况?会不会不满足上面推出的方程式?答案是肯定的,因为 \((2,3)\) 的上、左两个方向的数是 \(0\),则取 \(\min\) 之后就一定\(0\) 了。

经过以上分析以及一些证明,基本就有大致的代码框架了:

#include<iostream>
using namespace std;

int n, m, mp[505][505]; // mp 记录地图
char c; // 临时储存

int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; i ++) 
		for(int j = 1; j <= m; j ++) {
			cin >> c;
			mp[i][j] = c - '0'; // 转化为数字
		}
	for(int i = 1; i <= n; i ++) {
		for(int j = 1; j <= m; j ++) {
			int minn = min(min(mp[i - 1][j], mp[i + 1][j]), min(mp[i][j - 1], mp[i][j + 1])); // 计算 minn 值
			cout << minn; // 可直接输出
        // 把周围扩散的数减去
			mp[i - 1][j] -= minn; mp[i + 1][j] -= minn; 
			mp[i][j - 1] -= minn; mp[i][j + 1] -= minn;
		}
		cout << endl; // 记得换行
	}
	return 0;
}

提交记录

\[\text{The End!} \]

AT_abc178_d 题解

洛谷链接&Atcoder 链接

本篇题解为此题较简单做法较少码量,并且码风优良,请放心阅读。

题目简述

给定一个正整数 \(S\),问有多少个数满足以下条件:

  • 序列中必须为 \(\ge 3\) 的正整数。
  • 序列中的和必须为 \(S\)

思路

首先想到组合数学,本题可通过组合数学插板法解决。

引入:例题,求 \(n\) 个苹果分为 \(k\) 组的方案数每组苹果个数仅需 \(\ge 1\)

那么这道题就可转化为:

SonoSlack

如图,共有 \(n\) 个苹果用 \(k-1\)隔板隔开 \(k\) 组,即\(n-1\) 个空中选 \(k-1\) 个空插隔板,所以答案即为 \(C_{n-1}^{k-1}\)

注意:隔板不能重合也不能在两边,因为每组苹果个数需 \(\ge 1\)

现在再来看此题,发现每组需 \(\ge 3\)组数不确定,所以需要枚举组数 \(i\)\(1\)\(n/3\),并且空格数即可插板的位置数也会发生变化

\[n-1 \to n-2 \times i-1 \]

隔板数依然不变,是 \(i-1\)。所以对于此题,方案数即为 \(C_{n-2 \times i-1}^{i-1}\)

接下来就需要解决组合数的计算了,因为此题的数据范围不大,所以就可以用杨辉三角预处理组合数。学过组合数的同学应该都学过,这里直接说结论:杨辉三角的第 \(n\) 行的第 \(i\) 个数即为 \(C_n^i\) 的值

预处理如下(预处理过程中也要模 \(1000000007\)):

for(int i = 0; i <= n; i ++) {
		C[i][0] = C[i][j] = 1;
		for(int j = 1; j < i; j ++) 
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;
	}

经过以上分析及部分代码讲解,基本的代码框架就很清晰了,下面是具体代码实现

#include<iostream>
using namespace std;
#define MOD 1000000007

int S, C[2005][2005]; // 组合数数组
long long ans = 0; // 记录答案,可不开 long long

int main() {
	cin >> S;
	for(int i = 0; i <= S; i ++) {
		C[i][0] = C[i][j] = 1;
		for(int j = 1; j < i; j ++) 
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD; // 杨辉三角预处理组合数
	}
        // 枚举组数
	for(int i = 1; i <= S / 3; i ++) 
		ans = (ans + C[S - 2 * i - 1][i - 1]) % MOD; // 累计答案
        cout << ans << endl; // 输出答案,换行好习惯 
	return 0;
}

提交记录:

提交记录

\[\text{The End!} \]

A. 循环与非

题目描述

给定长度为 \(n\) 的序列 {\(a_n\)},每一个数字都不超过 \(2\)\(k\) 次方。给定 \(m\) 次操作,每次操作形如:

0 x y :将 \(a[x]\) 改为 \(y\)

1 x y :令 \(t=y\) NAND \(a[0]\) NAND \(a[1]\) NAND \(a[2]\) NAND ... NAND \(a[i\) \(mod\) \(n]\) NAND ... NAND \(a[(x*n-1)\) \(mod\) \(n]\)

NAND 代表按位与非操作。

求所有计算结果的 \(t\) 的异或和。

输入格式

第一行包含三个正整数 \(n,m,k\)

接下来一行 \(n\) 个整数 \(a_0 \sim a_{n-1}\)

接下来 \(m\) 行每行三个整数 \(op,x,y\)

输出格式

输出一行一个整数表示答案。

样例输入

2 3 3
1 2
1 2 3
0 0 3
1 2 2

样例输出

2

样例输入

7 4 4
4 6 4 7 10 9 11
1 5 7
1 7 14
0 6 7
1 6 5

样例输出

9

数据规模

\(1 \le n,m \le 10^4\)\(0 \le y < 2^k\)

操作 \(0\) 中,\(0 \le x < n\)

操作 \(1\) 中,\(1 \le x < 10^9\)

\(1 \le k \le 30\)

点击查看代码
#include<iostream>
#include<set>
using namespace std;

int m, n, k, a[10005];
set<int> st[30];

int find(int i) { return st[i].empty() ? -1 : *st[i].rbegin(); }

int main() {
	cin >> n >> m >> k;
	int tmp;
	for(int i = 0; i < n; i ++) {
		cin >> tmp;
		for(int j = 0; j < k; j ++)
			if(!(tmp & 1 << j)) st[j].insert(i);
	}
	int opt, x, y, res, t = 0, last;
	while(m --) {
		cin >> opt >> x >> y;
		if(opt == 0)
			for(int i = 0; i < k; i ++)
				if(!(y & 1 << i)) st[i].insert(x);
				else st[i].erase(x);
        else {
			res = 0;
			for(int i = 0; i < k; i ++){
				last = find(i);
				if(last == -1) res += (1ll * x * n + (y >> i & 1) & 1) << i;
				else res += (n - last & 1) << i;
			}
			t ^= res;
		} 
	}
	cout << t;
	return 0;	
}
编译结果
compiled successfully
time: 100ms, memory: 7232kb, score: 100, status: Accepted
> test 1: time: 1ms, memory: 3408kb, points: 10, status: Accepted
> test 2: time: 5ms, memory: 3448kb, points: 10, status: Accepted
> test 3: time: 25ms, memory: 7232kb, points: 10, status: Accepted
> test 4: time: 3ms, memory: 4076kb, points: 10, status: Accepted
> test 5: time: 17ms, memory: 5248kb, points: 10, status: Accepted
> test 6: time: 2ms, memory: 3544kb, points: 10, status: Accepted
> test 7: time: 10ms, memory: 3720kb, points: 10, status: Accepted
> test 8: time: 10ms, memory: 3604kb, points: 10, status: Accepted
> test 9: time: 18ms, memory: 4716kb, points: 10, status: Accepted
> test 10: time: 9ms, memory: 3476kb, points: 10, status: Accepted

B. 蹦蹦萝卜

题目描述

有一个环状的操场,操场被分割为 \(n\) 个小块,每个小块上写着一个数字。

有一只小白兔站在操场的起点 \(1\),它每次可以跳 \(k\) 个小块,然后拿走等同于它跳之前所站小块上数字数量的胡萝卜,问它跳 \(m\) 次,总共可以拿到几个胡萝卜?

小白兔在 \(i\) 处时,它跳到的下一个位置是 \((i+k)\) \(mod\) \(n + 1\)

如果能够算出来的话,小白兔就能把所有的胡萝卜都带回家吃啦!

答案对 \(10^9+7\) 取模。

输入格式

第一行包含两个正整数 \(n\)\(k\)

第二行 \(n\) 个正整数 \(a_1...a_n\) 每个小块上的数字,

第三行一个正整数 \(m\)

输出格式

输出一行一个整数表示答案。

样例输入

5 1
1 10 100 1000 10000
3

样例输出

10101

数据规模

\(1 \le k \le n \le 10^6\)\(1 \le a_i \le 10^6\), \(1 \le m \le 10^{18}\)

点击查看代码
#include<iostream>
#include<set>
using namespace std;

#define MOD 1000000007
int n, k, a[1000005], p[1000005][70], c[1000005][70];
long long m;

int main() {
    cin >> n >> k;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    cin >> m;
    for(int i = 1; i <= n; i ++) {
        p[i][0] = (i + k) % n + 1;
        c[i][0] = a[i];
    }
    for(int j = 1; j <= 60; j ++) {
        for(int i = 1; i <= n; i ++) {
            p[i][j] = p[p[i][j - 1]][j - 1];
            c[i][j] = (c[i][j - 1] + c[p[i][j - 1]][j - 1]) % MOD;
        }
    }
    long long res = 0;
    int x = 1;
    for(int j = 0; j < 63; j ++) {
        if(m & 1LL << j) {
            res += c[x][j];
            x = p[x][j];
        }
    }
    cout << res % MOD << endl;
    return 0;
}
编译结果
compiled successfully
time: 12253ms, memory: 496952kb, score: 100, status: Accepted
> test 1: time: 1775ms, memory: 386052kb, points: 10, status: Accepted
> test 2: time: 106ms, memory: 58732kb, points: 10, status: Accepted
> test 3: time: 1147ms, memory: 262252kb, points: 10, status: Accepted
> test 4: time: 324ms, memory: 116136kb, points: 10, status: Accepted
> test 5: time: 1937ms, memory: 410032kb, points: 10, status: Accepted
> test 6: time: 273ms, memory: 99752kb, points: 10, status: Accepted
> test 7: time: 2433ms, memory: 496952kb, points: 10, status: Accepted
> test 8: time: 1629ms, memory: 360016kb, points: 10, status: Accepted
> test 9: time: 1412ms, memory: 313324kb, points: 10, status: Accepted
> test 10: time: 1217ms, memory: 275484kb, points: 10, status: Accepted

C. 超大范围幂运算

时间:1s 空间:256M

题目描述

\(3\) 个整数 \(a, b, c\),求 \(a^b\) \(mod\) \(c\)

输入格式

第一行包含一个整数 \(T\),表示测试数据组数。

对于每组测试数据,包含 \(3\) 个整数 \(a, b, c\)

输出格式

对于每组测试数据,输出一个整数表示答案。

样例1输入

6
0 0 1
0 0 100
2 5 17
0 10 100
100 0 1000000000000000000
114 514 1919810

样例1输出

0
1
15
0
1
290606

约定与提示

对于 \(100\%\) 的数据,\(1 \le T \le 5000\)\(0 \le a,b \le 10^{18}\); \(1 \le c \le 10^{18}\)

点击查看代码
#include<iostream>
#include<set>
using namespace std;

long long T, a, b, c;

long long qadd(long long a, long long b, long long p) {
    long long res = 0;
    while(b) {
        if(b & 1) res = (res + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return res;
}

long long quickpow(long long x, long long n, long long m){
	if(n == 0)	return 1 % m;
	long long res = quickpow(qadd(x, x, m), n / 2, m);
	if(n & 1) res = qadd(res, x, m);
	return res;
}

int main() {
    cin >> T;
    while(T --) {
        cin >> a >> b >> c;
        printf("%lld\n", quickpow(a, b, c));
    }
    return 0;
}
编译结果
compiled successfully
time: 597ms, memory: 3524kb, score: 100, status: Accepted
> test 1: time: 1ms, memory: 3424kb, points: 0, status: Accepted
> test 2: time: 1ms, memory: 3524kb, points: 20, status: Accepted
> test 3: time: 1ms, memory: 3492kb, points: 20, status: Accepted
> test 4: time: 50ms, memory: 3484kb, points: 20, status: Accepted
> test 5: time: 67ms, memory: 3476kb, points: 20, status: Accepted
> test 6: time: 477ms, memory: 3496kb, points: 20, status: Accepted
posted @ 2023-07-26 13:50  So_noSlack  阅读(183)  评论(0编辑  收藏  举报