CSP-J复赛模拟赛4 王晨旭补题 2024.10.4

前言:

每次把精力押宝在一个题上总没有好结果,本次4题均跟打表有关

一,成绩

1.三个【100/100】第一题稳定发挥

2.合体【70/100】每个第二题都平等的恨我

3.矩阵【0/100】没捐但胜似捐了

4.数对【0/100】每日一捐x1

还不错?但有些失落感,什么时候那美丽的第三,四题能眷顾我呢?【170】= 【170】

二,题一

p1:相当经典的一个问题,现在看起来较为简单,但很多人卡(包括本人很久之前做的时候,同位卡了半个世纪了(昨日box满分的人),类似于这种的模拟推理题,我的方法画导图,列数据,(输)出过程,套样例,这次没有第一时间了解题目,浪费一定时间(仍小于25分钟)注意读好题后下手

p2:题面

现在科学家在培养 A,B,C 三种微生物,这三种微生物每一秒都会繁殖出新的微生物,具体规则为:

A 类微生物每一秒会繁殖出 1 个 A 类微生物,1 个 B 类微生物,1 个 C 类微生物。
B 类微生物每一秒会繁殖出 2 个 A 类微生物,2 个 C 类微生物。
C 类微生物每一秒会繁殖出 1 个 A 类微生物,1 个 B 类微生物。

假设所有的微生物都不会死亡,一开始培养皿中有 A,B,C 三种微生物各 1 个,现在问你 n 秒后 A,B,C 三种微生物分别有奇数个还是偶数个。

p3:这题我做的很清晰,所以直接放我的思路

直接循环n秒达表(注意第0秒状态为1)

现在这1秒的A类(微生物)(a[i]):前一秒的A类(a[i-1])+前一秒的A类生成的A类(a[i-1])+前一秒的B类生成的2个A类(b[i-1]*2)+前一秒的C类生成的A类(c[i-1])

现在这1秒的B类(微生物)(b[i]):前一秒的B类(b[i-1])+前一秒的A类生成的B类(a[i-1])+前一秒的C类生成的B类(c[i-1])

现在这1秒的C类(微生物)(c[i]):前一秒的C类(c[i-1])+前一秒的A类生成的C类(a[i-1])+前一秒的B类生成的2个C类(b[i-1]*2)

p4:AC代码

#include<iostream>
#include<cstdio>
using namespace std;
long long a[1000006],b[1000006],c[1000006];
int main(){
	int n;
	cin>>n;
	a[0]=1;
	b[0]=1;
	c[0]=1;
	for(int i=1;i<=n;i++){
		a[i]=(a[i-1]+b[i-1]*2+c[i-1]+a[i-1])%2;
		b[i]=(a[i-1]+c[i-1]+b[i-1])%2;
		c[i]=(a[i-1]+b[i-1]*2+c[i-1])%2;
	}
	
	if(a[n]%2==0){
		cout<<"even"<<endl;
	}
	else if(a[n]%2==1){
		cout<<"odd"<<endl;
	}
	if(b[n]%2==0){
		cout<<"even"<<endl;
	}
	else if(b[n]%2==1){
		cout<<"odd"<<endl;
	}
	if(c[n]%2==0){
		cout<<"even"<<endl;
	}
	else if(c[n]%2==1){
		cout<<"odd"<<endl;
	}
}

三,题二

p1:又是scanf/printf惨案!!!!!!!!!!!!!

p2:少30分,这辈子不敢写cin,cout了,本题切换输入输出后AC,做的时间很短,思路好想,中心在于桶的使用,加打表递推,注意m大小

1\leq m\leq 10^{6}

p3:水题,但我的思路较为累赘,还是官方思路好一些

考虑到数字大小不大。我们可以记录一个答案数组ans[i]。
ans[i]表示为查询 i 的答案。也就是ans[i]表示最多可以产生多少个i。直接统计每个数字的数量cnt[i],进行递推。
ans_{i}=\left \lfloor \frac{ans_{i-1}}{2} \right \rfloor+cnt,其中 cnt 表示数字 i 出现的次数。 
然后直接递推即可。
对于查询,O(1)输出即可。

总之,预处理打表,怎么做都行

p4:先放我的代码,再上官方代码,功能相同(endl时间巨长,以后避免使用)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
long long a[1000005],t[1000005],kl[1000005];
int main(){
	//freopen("fit.in","r",stdin);
	//freopen("fit.out","w",stdout);
	int n;
	cin>>n;
	int m;
	cin>>m;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		t[a[i]]++; 
		kl[a[i]]++;
	}
	int q;
	cin>>q;
	int x;
	for(int i=1;i<=m;i++){
		t[i+1]+=floor(t[i]/2); 
		t[i]/=2; 
	}
	while(q--){
		scanf("%d",&x);
		printf("%d\n",kl[x]+t[x-1]);
		//cout<<kl[x]+t[x-1]<<endl;
	}
	return 0;
}
(int x后替换)
//for(int i=2;i<=m;i++){
//	  t[i]+=t[i-1]/2;
//}
//while(q--){
//    scanf("%d",&x);
//    printf("%d\n",t[x]);
//} 

四,题三

p1:感受到了这个题的强度,比昨天第二题还猛,又是熟悉的的多思路选一,又是写了改写了改,摸不到样例,但它是第三题,又符合我的思维所以做起来较为舒服,第零种思路,不知道如何使用^,想着转二后异或完转十,想起^后遂放弃,第一个思路,第一次写二维异或和,没有信心,没有主动使用^运算符过,没有经验,再加上不清楚^的逆运算(就是它本身),写对了也放弃,第二个思路,六层循环枚举子矩阵的左上角,右上角(4层),然后遍历子矩阵中所有数字求异或和(2层),得20分,样例正确答案822,最接近的829,放弃,第三个思路,有样例数组仅包含1,单数个1的异或和为1,双数个为0,跟据规律可以由数字个数判断异或和,但样例数组范围过大,遂放弃。

tip:样例展示

对于测试点 1\sim 41\le n,m\le 10,0\le g_{i,j} < 2^{10}
对于测试点 5\sim 61\le n,m\le 300,g_{i,j}=1
对于测试点 7\sim 121\le n,m\le 300,0\le g_{i,j}\le 1
对于测试点 13\sim 201\le n,m\le 300,0\le g_{i,j} < 2^{10}

p2:题面

现在给你一个 n 行 m 列的矩阵,矩阵上每个格子有一个整数,其中第 i 行第 j 列对应的格子上的整数为 g_{i,j}
现在定义该矩阵的一个子矩阵的快乐值为该子矩阵上的所有数字的异或和。

一组数字 a_1,a_2,...,a_n​​ 的异或和为 a_1\space xor\space a_2\space xor\space ... \space xor \space a_n。(其中 xor 表示按位异或运算)

现在问你,该矩阵的所有子矩阵的快乐值之和为多少?

p3:思路

考虑将二维数组压成一维的;
枚举起始行 i 和终止行 j ,这个范围内的每一列都求异或值 x[k] ;即 x[k] 为 a[i][k] ~ a[j][k] 的异或值。可以按位去考虑,对于某个区间,按位异或之后的值,的某一位,是否为1。只需要考虑这个区间内的这一位的1的个数是奇数个还是偶数个。也可以考虑xo数组 ans += (xo[r] ^ xo[l - 1]),按位考虑,某一位如果想被累计到答案里,那么xo[r]的这一位和xo[l-1]的这一位不相同。所以可以考虑把xo拆位,如果当前这一位是1,那么就考虑它前面这一位是0的情况有多少个,反之同理。

p4:代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,a[305][305],b[305],sum[305],cnt[305]; 
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
		}
	}
	long long ans;
	for(int u=1;u<=n;u++){
		memset(b,0,sizeof(b));
		for(int d=u;d<=n;d++){
			for(int j=1;j<=m;j++){
				b[j]^=a[d][j];
				sum[j]=sum[j-1]^b[j];
			}
			for(int p=0;p<10;p++){
				long long cnt[2]={1,0};
				for(int j=1;j<=m;j++){
					int t=(sum[j]>>p)&1;
					ans+=(1<<p)*cnt[t^1];
					cnt[t]++;
				}
			}
		}
	}
	cout<<ans;
}

 五,题四

p1:这题骗分似乎更适合我,可惜了,先看的第三题,就直接干到结束了,第四题题都没读

tip:输出0拿到五分

p2:题面,求逆序对个数,暴力可拿分

给你一个长度为 n 的数列 a​1​​,a​2​​,...,a​n​​。
再给你一个长度为 m 的数列 b​1​​,b​2​​,...,b​m​​。
现在再再再给你一个正整数 p,让你生成一个长度为 n×m 的数列 c​1​​,c​2​​,...,c​n×m​​。
其中满足 c​(i−1)×m+j​​=(a​(i)​​+b​(j)​​) mod p。
现在问你数列 c 中有多少个数对 (i,j) 满足 i<j 且 c​i​​>c​j​​?

p3:思路  __int128首亮相,高精度押题正确

我们观察到c数组是可以分成n块,每一块有m个数字。 然后我们求逆序对可以块内求,然后再块与块之间求。
对于块内
观察到值域非常小,我们可以对 b 数组记录数字出现次数num。
枚举到当前数字x,计算逆序数时,可以枚举比x大的数字的出现次数即可。也就是记录 num[b[i]+1]~num[p-1] 的和。
考虑b后续需要变化(+a[j]) 。所有我们记录一个数组nixu[k]。表示为对于b数组所有数组都加k之后,逆序数为多少。
对于块与块
我们可以记录之前所有数字的出现次数,当前块一定是在之前块的后面,所以直接枚举值域,统计逆序数即可。

tip2:long long得30分

p4:正解(__int128版本) 注意__int128不可以直接输入输出

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#define M 2000005
using namespace std;
int n, m, p, a[M], b[M];
long long num[10], numb[10], nixu[10]; // nixu[i]表示b数组+i之后的逆序数。
int main() {
	cin >> n >> m >> p;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i <= m; i++) {
		cin >> b[i];
		numb[b[i]]++;
	}
	for (int j = 0; j < p; j++) {
		memset(num, 0,sizeof(num));
		for (int i = 1; i <= m; i++) {
			for (int k = b[i] + 1; k < p; k++)
				nixu[j] += num[k];
			num[b[i]]++;
			b[i] = (b[i] + 1) % p;
		}
	}
	memset(num, 0, sizeof(num));
	__int128 ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += nixu[a[i]];
		for (int j = 0; j < p; j++) {
			int x = (j + a[i]) % p;
			for (int k = x + 1; k < p; k++)
				ans += 1ll * numb[j] * num[k];
		}
		for (int j = 0; j < p; j++)
			num[(j + a[i]) % p] += numb[j];
	}
	vector<int> ans_s;
	if (ans == 0)
		cout << 0 << endl;
	else {
		while (ans > 0) {
			ans_s.push_back(ans % 10);
			ans /= 10;
		}
		for (int i = ans_s.size() - 1; i >= 0; i--)
			cout << ans_s[i];
		cout << endl;
	}
	return 0;
}

六,总结

不很满意,思路需要调整,对于一二题来说需更加谨慎,目前三四题仅能处理其中一个,做之前要选好,注意输入输出!

七,祝福

祝:幸福安康

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值