模拟四补题报告
李智航
S13494
1.考试得分:
第一题AC,第二题70分,第三题,第四题
2.考试过程:
第一题感觉还行,突然发现A,B,C的值存不下,必须边算边取余,浪费了很多时间;第二题数据范围也很恶心,感觉不优化就爆, 对于查找也很恶心,有时候会把原先的覆盖掉,还需要记忆。第三天卡的更死,打了一个找所有子序列的代码,六重循环,完全崩了(也就是枚举)。第四题直接不会做,看了一眼样例,偏分都没法骗分2,只好搬出了rand()碰运气。
3.考试解析
1.下三个(three)
1.题目:
2.开始代码和思路
代码如下:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
long long A=1,B=1,C=1;
long long CA,CB,CC;
for(int i=1;i<=n;i++){
CA=A+A+2*B+C;
CB=B+A+C;
CC=C+A+2*B;
A=CA;
B=CB;
C=CC;
if(A%2!=0){
A=1;
}else{
A=0;;
}
if(B%2!=0){
B=1;
}else{
B=0;
}
if(C%2!=0){
C=1;
}else{
C=0;
}
}
if(A==1){
cout<<"odd"<<endl;
}else{
cout<<"even"<<endl;
}
if(B==1){
cout<<"odd"<<endl;
}else{
cout<<"even"<<endl;
}
if(C==1){
cout<<"odd"<<endl;
}else{
cout<<"even"<<endl;
}
return 0;
}
思路:就是暴力枚举,因为数据范围太大,所以边枚举边判段是奇数还是偶数。
3.正解
sub1:大模拟
#include<bits/stdc++.h>
using namespace std;
const int M=1e5+10;
int f[M][3], n;
int main(){
cin >> n;
f[0][0] = 1;
f[0][1] = 1;
f[0][2] = 1;
for (int i = 0; i < n; i++){
f[i + 1][0] = f[i][0];
f[i + 1][1] = f[i][1];
f[i + 1][2] = f[i][2];
f[i + 1][0] += f[i][0];
f[i + 1][0] %= 2;
f[i + 1][1] += f[i][0];
f[i + 1][1] %= 2;
f[i + 1][2] += f[i][0];
f[i + 1][2] %= 2;
f[i + 1][0] += f[i][1] * 2 % 2;
f[i + 1][0] %= 2;
f[i + 1][2] += f[i][1] * 2 % 2;
f[i + 1][2] %= 2;
f[i + 1][0] += f[i][2];
f[i + 1][0] %= 2;
f[i + 1][1] += f[i][2];
f[i + 1][1] %= 2;
}
cout << (f[n][0] ? "odd" : "even") << endl;
cout << (f[n][1] ? "odd" : "even") << endl;
cout << (f[n][2] ? "odd" : "even") << endl;
return 0;
}
sub2:思维
#include<iostream>
#include<cstdio>
using namespace std;
int n;
int main(){
scanf("%d",&n);
if(n%3==0){
printf("odd\n");
printf("odd\n");
printf("odd\n");
}
else if(n%3==1){
printf("odd\n");
printf("odd\n");
printf("even\n");
}
else if(n%3==2){
printf("even\n");
printf("even\n");
printf("odd\n");
}
return 0;
}
2.合体(fit)
1.题目:
开始代码:
#include <iostream>
#include <vector>
using namespace std;
int a[1000001],count[1000001];
bool vis[1000001];
int remember[1000001];
#define endl "\n"
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;++i){
cin>>a[i];
count[a[i]]++;
}
int q;
cin>>q;
for(int i=0;i<q;++i){
int x;
cin>>x;
if(x>m){
cout<<0<<endl;
continue;
}else if(x==1){
cout<<count[1]<<endl;
continue;
}
if(vis[x]==false){
for(int j=1;j<x;++j){
if(count[j]==1||count[j]==0);
else{
if(count[j]%2==0){
count[j+1]+=count[j]/2;
count[j]=0;
}
else{
count[j+1]+=count[j]/2;
count[j]=1;
}
remember[j+1]=count[j+1];
vis[x]=true;
}
}
}else{
cout<<remember[x]<<endl;
continue;
}
cout<<count[x]<<endl;
}
return 0;
}
思路:直接暴力。把每个数枚举到x,每次枚举时,每个经过枚举的数都是最大的。
因为j是去x的大小的,所以刚算出的j+1位是最大的,然后remember存储一下,防
止以后的再来查找。那个vis就是判断x会不会出现重复,x重复了话就不进入循环。
正解:
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e6+25;
int n,m,a[N],q,x;
int t[N],k[N];//t为桶数组,k为合并数组
int minn=N;
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
t[a[i]]++;
minn=min(a[i],minn);
}
for(int i=minn;i<=m+25;i++){//极端情况出现10^6个相同的数,最多合成log2(10^6)个新数≈20个
k[i]=t[i-1]/2;//每个数的合并量=上个数的个数/2
t[i]+=k[i];//当前数的个数=本身个数+合并量
}
scanf("%d",&q);
while(q--){
scanf("%d",&x);
printf("%d\n",t[x]);
}
return 0;
}
3.矩阵(matrix)
1.题目:
2.开始代码:
#include <iostream>
using namespace std;
long long a[301][301];
long long ans,count;
int main(){
int n,m;
cin>>n>>m;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
cin >> a[i][j];
}
}
for (int x1 = 0; x1 < n; ++x1) {
for (int y1 = 0; y1 < m; ++y1) {
for (int x2 = x1; x2 < n; ++x2) {
for (int y2 = y1; y2 < m; ++y2) {
int count=0;
for (int i = x1; i <= x2; ++i) {
for (int j = y1; j <= y2; ++j) {
count=count^a[i][j];
}
}
ans+=count;
}
}
}
}
cout<<ans;
return 0;
}
思路:该程序旨在计算二维数组中所有子矩阵的异或总和。首先,输入矩阵的维度和元素。接着,利用四重循环遍历所有可能的子矩阵,通过指定左上角和右下角的坐标,逐个计算子矩阵的异或值。内层的双重循环用于计算当前子矩阵内所有元素的异或。最后,将计算得到的异或值累加到答案中,并输出结果。此方法的时间复杂度较高,适合较小的矩阵。
3.正解:
//子矩阵异或,二维前缀和做法求二维异或和O(n^4)
//二维压一维
/*
枚举起始行i和终止行j
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
x[k]=a[i][k]~a[j][k] -->一维前缀异或和
0 按位异或一个数的结果是1,代表这个数里1的个数为奇数;结果为0,为偶数
*/
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=305;
int n, m, a[N][N], x[N], xo[N];
int main(){
cin >> n >> m;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
cin >> a[i][j];
}
}
ll ans=0;
for(int i=1; i<=n; i++){//枚举起始行
memset(x, 0, sizeof x);
for(int j=i; j<=n; j++){//枚举终止行
for(int k=1; k<=m; k++){
x[k]^=a[j][k];
xo[k]=xo[k-1]^x[k];
}
for(int p=0; p<10; p++){
//按位枚举,拆位,如10101=10000+00100+00001,选带1的,结果最大
int cnt0=1;//记录第p位为0的个数
int cnt1=0;//记录第p位为1的个数
for(int k=1; k<=m; k++){
if(xo[k] & (1<<p)){//如果当前是1
ans+=1ll*(1<<p)*cnt0;
//异或取前面“有意义的”的0的个数,1^0=1
cnt1++;
}
else{
ans+=1ll*(1<<p)*cnt1;
异或取前面“有意义的”的1的个数,1^0=1
cnt0++;
}
}
}
}
}
cout << ans;
return 0;
}
思路:该程序通过二维前缀异或和的方式高效计算所有子矩阵的异或和。首先,输入矩阵的维度和元素。然后,枚举起始行和终止行,对每一列进行异或操作,形成一维前缀异或数组。接着,对于每一位二进制位,通过拆位计算当前子矩阵中1和0的个数,利用这些信息更新总异或和。该方法有效降低了时间复杂度,使得在较大矩阵中也能迅速求解。
4.数对(pair)
1.题目:
开始代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<rand()%500;
return 0;
}
没有一点思路,就是靠运气
3.正解
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+5, M=1e6+5;
bool flag=0;//特殊样例全0
int n, m, p, a[M], b[M];
ll num[10], numb[M], nixu[10];//nixu[i]表示b数组+i之后的逆序对个数
ll ans[200], cnt=0;
void jia(ll k){
ans[0]+=k;
int pos=0;
while(1){
if(ans[pos]>=1000000){//压位高精,一个变量存6位,不再存1位
ans[pos+1]+=ans[pos]/1000000;
ans[pos]%=1000000;
if(++pos>cnt){
cnt++;
}
}
else break;
}
}
int main(){
cin >> n >> m >> p;
for(int i=1; i<=n; i++) {
cin >> a[i];
if(a[i]!=0) flag=1;
}
for(int i=1; i<=m; i++){
cin >> b[i];
if(b[i]!=0) flag=1;
numb[b[i]]++;
}
//预处理,nixu[i]存储a[i]加到b[1]~b[m]各个数上的逆序对数
for(int j=0; j<p; j++){//a、b数组范围在0~9,打表预处理,
memset(num, 0, sizeof num);
for(int i=1; i<=m; i++){//枚举b
for(int k=b[i]+1; k<p; k++){//k=b[(i+j)%p]+1;
//保证数字比b[i]大
nixu[j] += num[k];
}
num[b[i]]++;//num[(i+j)%p]++;
//记录b[i]次数,为下次循环准备
b[i]=(b[i]+1)%p;//注释掉
//保证自增,代替掉模拟a[i]+b[j]的枚举,因为a、b数组元素皆<p
}
}
memset(num, 0, sizeof num);
//ll ans=0;
for(int i=1; i<=n; i++){
//ans+=nixu[a[i]];//块内的逆序对数量,c[(i-1)%m+1]~c[(i-1)*m+m]
jia(nixu[a[i]]);
for(int j=0; j<p; j++){
int x=(j+a[i])%p;//某一个c的值
for(int k=x+1; k<p; k++){
//ans+=1ll*numb[j]*num[k];
//块与块之间的个数(nm太大只能求块与块加、间的),不做优化30
ll x=1ll*numb[j]*num[k];
jia(x);
}
}
for(int j=0; j<p; j++){
num[(j+a[i])%p]+=numb[j];
}
}
if(!flag) cout << 0;
else{
cout << ans[cnt];
for(int i=cnt-1; i>=0; i--){
printf("%06lld", ans[i]);
}
}
return 0;
}
思路:这个程序的目的是计算两个数组 a 和 b 之间的逆序对数。具体思路如下:读入数据:首先,输入数组 a(长度为 n)和 b(长度为 m),同时检查是否有非零元素,以便后续处理。预处理逆序对:使用一个长度为 p 的数组 nixu 来存储将 a[i] 加到 b 每个元素后产生的逆序对数。通过遍历 b 数组并记录当前元素出现的次数来计算逆序对。计算逆序对总数:遍历数组 a,对于每个 a[i],直接获取在 nixu 中记录的逆序对数。计算块与块之间的逆序对数,通过更新 num 数组来维护各个模 p 值的出现次数,并结合当前的元素 a[i] 来更新结果。高精度输出:由于结果可能很大,使用一个数组 ans 来存储每6位数字,最后进行格式化输出。最终输出逆序对的总数,如果所有元素均为零则输出0。这种方法通过预处理和动态更新,有效地降低了时间复杂度,使得即使在大的输入下也能快速计算结果。
总结
思路还需要加强,时间复杂度的优化也需要加强。