8.3集训模拟赛题解
A.斐波那契
题目描述
小 C 养了一些很可爱的兔子。
有一天,小 C 突然发现兔子们都是严格按照伟大的数学家斐波那契提出的模型来进行繁衍:一对兔子从出生后第二个月起,每个月刚开始的时候都会产下一对小兔子。我们假定,在整个过程中兔子不会出现任何意外。
小 C 把兔子按出生顺序,把兔子们从 1 开始标号,并且小 C 的兔子都是 1 号兔子和 1 号兔子的后代。如果某两对兔子是同时出生的,那么小 C 会将父母标号更小的一对优先标号。如果我们把这种关系用图画下来,前六个月大概就是这样的:
其中,一个箭头 A → B 表示 A 是 B 的祖先,相同的颜色表示同一个月出生的兔子。
为了更细致地了解兔子们是如何繁衍的,小 C 找来了一些兔子,并且向你提出了 m 个问题:她想知道关于每两对兔子 ai 和 bi,他们的最近公共祖先是谁。你能帮帮小 C 吗?
一对兔子的祖先是这对兔子以及他们父母(如果有的话)的祖先,而最近公共祖先是指两对兔子所共有的祖先中,离他们的距离之和最近的一对兔子。比如 5 和 7 的最近公共祖先是 2,1 和 2 的最近公共祖先是 1,6 和 6 的最近公共祖先是 6。
输入格式
输入第一行,包含一个正整数 m。
输入接下来 m 行,每行包含 2 个正整数,表示 ai 和 bi。
输出格式
输入一共 m 行,每行一个正整数,依次表示你对问题的答案。
样例
\(input\)
5
1 1
2 3
5 7
7 13
4 12
\(output\)
1
1
2
2
4
思路
第N个月新生的兔兔的数量就是\(F_{n−2}\),而他们的父亲分别是从1号到\(F_{n−2}\)号,那么由于中间隔了\(F_{n−1}\)代兔兔,则第 \(N\) 代每只新生兔兔的父亲都应是其编号减去\(F_{n−1}\),这是因为每只新生兔的编号都是按其父亲的编号由小到大而来的。对于给出的每两只兔,我们按此规律令它们不断交替向上跳,即可找到答案,过程中可以用二分优化。
代码实现
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 100;
char buf[1 << 20], *p1, *p2;
char getc() {
if(p1 == p2) {
p1 = buf;
p2 = buf + fread(buf, 1, 1 << 20, stdin);
if(p1 == p2) return EOF;
}
return *p1++;
}
inline long long read() {
long long s = 0, w = 1; char c = getc();
while(c < '0' || c >'9') {if(c == '-')w = -1; c = getc();}
while(c >= '0' && c <= '9') s = s * 10 + c - '0', c = getc();
return s * w;
}
long long fac[maxn], m, a, b;
int main() {
m = read();
fac[0] = 1, fac[1] = 1;
for(register int i = 2; i <= 60; i++)
fac[i] = fac[i - 1] + fac[i - 2];
while(m--) {
a = read(), b = read();
while(a != b) {
if(abs(a - b) == 1) {
break;
}
if(a < b) swap(a, b);
long long l = 1, r = 60, mid;
while(l <= r) {
mid = (l + r) >> 1;
if(fac[mid] < a) l = mid + 1;
else r = mid - 1;
}
long long z = a;
a = b;
b = z - fac[l - 1];
}
if(a == b) printf("%lld\n", a);
else printf("1\n");
}
return 0;
}
B.数颜色
题目描述
小\(C\)的兔子不是雪白的,而是五彩缤纷的。每只兔子都有一种颜色,不同的兔子可能有相同的颜色。小\(C\)把她标号从1到\(n\)的\(n\)只兔子排成长长的一排,来给他们喂胡萝卜吃。排列完成后,第i只兔子的颜色是\(a_i\)。
俗话说得好,"萝卜青菜,各有所爱"。小\(C\)发现,不同颜色的兔子可能有对胡萝卜的不同偏好。比如,银色的兔子最喜欢吃金色的胡萝卜,金色的兔子更喜欢吃胡萝卜叶子,而绿色的兔子却喜欢吃酸一点的胡萝卜……为了满足兔子们的要求,小\(C\)十分苦恼。所以,为了使得胡萝卜喂得更加准确,小\(C\)想知道在区间\([lj,rj]\)里有多少只颜色为\(c_j\)兔子。
不过,因为小\(C\)的兔子们都十分地活跃,它们不是很愿意待在一个固定的位置;与此同时,小\(C\)也在根据她知道的信息来给兔子们调整位置。所以,有时编号\(x_j\)和\(x_j+1\)的两只兔子会交换位置。
小\(C\)被这一系列麻烦事给难住了。你能帮帮她吗?
输入格式
输入第 1 行两个正整数\(n\),\(m\)。
输入第 2行\(n\)个正整数,第\(i\)个数表示第i只兔子的颜色\(a_i\)。
输入接下来\(m\)行,每行为以下两种中的一种:
1 \(l_j\) \(r_j\) \(c_j\):询问在区间\([l_j,r_j]\)里有多少只颜色为cj的兔子;
2 x j: \(x_j\)和\(x_{j+1}\)两只兔子交换了位置
输出格式
对于每个1操作,输出一行一个正整数,表示你对于这个询问的答案。
样例
\(input\)
6 5
1 2 3 2 3 3
1 1 3 2
1 4 6 3
2 3
1 1 3 2
1 4 6 3
\(output\)
1
2
2
3
思路
显然看数据范围,这道题数组是开不下的,所以我们可以使用vector来统计当前颜色的所有下标,然后用二分查找下表的位置,得出区间内的个数,具体看代码。
代码实现
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
const int maxn = 3e5+50;
using namespace std;
int a[maxn];
int n, m;
vector<int> place[maxn];
char buf[1 << 20], *p1, *p2;
char getc(){///%%%张大佬的神读,保我没有被学校的辣鸡评测机卡住
if(p1 == p2){
p1 = buf;
p2 = buf + fread(buf, 1, 1 << 20, stdin);
if(p1 == p2) return EOF;
}
return *p1++;
}
inline int read(){
int x = 0, f = 1; char ch = getc();
for(;!isdigit(ch); ch = getc()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getc()) x = (x<<1) + (x << 3) + ch - '0';
return x * f;
}
int main(){
n = read(), m = read();
for(register int i = 1; i <= n; i++){
a[i] = read();
place[a[i]].push_back(i);
}
int sol;
int x, y, z;
int iter1, iter2;
for(register int i = 1; i <= m; ++i){
sol = read();
if(sol == 1){
x = read(), y = read(), z = read();
iter1 = lower_bound(place[z].begin(), place[z].end(), x) - place[z].begin();
iter2 = upper_bound(place[z].begin(), place[z].end(), y) - place[z].begin() - 1;
printf("%d\n", iter2 - iter1 + 1);
}else{
x = read();
iter1 = lower_bound(place[a[x]].begin(), place[a[x]].end(), x) - place[a[x]].begin();
place[a[x]][iter1]++;
iter2 = lower_bound(place[a[x+1]].begin(), place[a[x+1]].end(), x+1) - place[a[x+1]].begin();
place[a[x+1]][iter2]--;
swap(a[x], a[x+1]);
}
}
return 0;
}
C.矩阵游戏
题目描述
\(LZK\)发明了一个矩阵游戏,大家一起来玩玩吧,有一个\(N\)行\(M\)列的矩阵。第一行的数字是1,2,...,\(M\),第二行的数字是\(M+1\),\(M+2\),...\(2\)\(\times\)\(M\),以此类推,第N类的数字是\((N-1)\)\(\times\)\(M+1\),\((N-1)\times(M+2)\)...\(N\)\(\times\)\(M\)。
例如,\(N=3\),\(M=4\)的矩阵是这样的:
1 2 3 4
5 6 7 8
7 10 11 12
对于身为智慧之神的\(LZK\)来说,这个矩阵过于无趣,于是他决定改造这个矩阵改造会进行\(K\)次,没次改造会将矩阵的某一行或某一列乘上一个数字,你的任务是计算最终这个矩阵内所有的数字之和,输出答案对\(10^9+7\)取模。
输入格式
第一行包含三个正整数\(N,M,K\),表示矩阵的大小与改造次数。接下来的行,每行会是如下两种形式之一:
\(R,X,Y\),表示将矩阵的第\(X(1\)\leq\(X\)\leq\(N)\)行变为原来的\(Y(0 \leq Y\leq10^9)\)倍。
\(S,X,Y\),表示将矩阵的第\(X(1\)\leq\(X\)\leq\(N)\)列变为原来的\(Y(0 \leq Y\leq10^9)\)倍。
输出格式
输出一行一个整数,表示最终矩阵内所有元素的和对\(10^9+7\)取模的结果。
样例
\(input\)
3 4 4
R 2 4
S 4 1
R 3 2
R 2 0
\(output\)
94
\(input\)
2 4 4
S 2 0
S 2 3
R 1 5
S 1 3
\(output\)
80
思路
这样暴力去算的话肯定会超时,不妨推推式子,\(sum_j=\sum _{i=1}^{n}\times r_i \times ((i-1) \times m + j)\),\(r_i\)表示的是第i行所有乘法的乘积,即把每次乘的数归在一起一次乘,\(sum_j\)表示第\(j\)列的和,然后把式子拆开,得到\(sum_j=\sum _{i=1}^{n}\times r_i \times (i-1) \times m+\sum _{i=1}^{n}\times r_i \times j\),所以只需要预处理出来\(\sum _{i=1}^{n}\times r_i \times (i-1)\)以及\(\sum _{i=1}^{n}\times r_i\),在\(for\)循环跑一遍每列,就可以大大提高时间效率。
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;
const int mod = 1e9+7;
const int maxn = 1e6+50;
inline int read(){
int x = 0, f = 1; char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, m, k;
int hang[maxn], line[maxn];
signed main(){
n = read(), m = read(), k = read();
char ch;
int maxx = max(n, m);
for(register int i = 1; i <= maxx; i++) hang[i] = line[i] = 1;
int x, y;
for(register int i = 1; i <= k; i++){
scanf(" %c", &ch);
if(ch == 'R'){
x = read(), y = read();
hang[x] = (hang[x] * y) % mod;
}else{
x = read(), y = read();
line[x] = (line[x] * y) % mod;
}
}
long long sol1 = 0, sol2 = 0;
for(register int i = 1; i <= n; i++){
sol1 = (sol1 + hang[i] * (i - 1) % mod * m % mod + mod) % mod;
sol2 = (sol2 + hang[i]) % mod;
}
long long ans = 0;
for(int j = 1; j <= m; j++){
ans = (ans + (sol1 + sol2 * j) % mod * line[j] % mod + mod) % mod;
}
cout<<ans<<endl;
}