CSP-J第四场模拟赛补题报告2024.10.4

一、得分情况

T1 100pts , T2 70pts , T3 10pts , T4 30pts ,总分210pts。赛后AC。

二、比赛概况

5min的时候拉肚子 

第一题看上去是O(1)的时间复杂度,就打了一个暴力推规律,一眼就看出来了,10min过了第一题。

第二题我想了递推算法,花20min过掉。

第三,四题不会,想要骗分,打了特殊样例。

最后发现第二题被卡常卡了30pts

三、解题报告

T1 三个(three)

得分情况

比赛时AC。

题目大意 

现在科学家在培养 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 三种微生物分别有奇数个还是偶数个。

解题思路

暴力即可。

暴力过程中若观察就会发现,微生物个数的奇偶性以三为周期有规律性,可以O(1)做出来。

赛时代码

#include<bits/stdc++.h>
using namespace std;
long long n;
int main(){
	cin>>n;
	if(n%3==2)cout<<"even\neven\n";
	else cout<<"odd\nodd\n";
	if(n%3==1)cout<<"even";
	else cout<<"odd";
	return 0;
} 

正解代码

#include<bits/stdc++.h>
using namespace std;
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;
}

T2 合体(fit)

得分情况

比赛时70pts。没有优化常数。

题目大意

有 n 个整数,可能相同。两个值为 i 的数可以(也可以不)合成一个值为 i + 1 的数。q 组询问,每组一个整数,问最多可能有几个这个整数。

解题思路

递推是正解,定义一个桶,盛放数字个数,遍历一边桶,若可以合成就合成到下一个数字,保持原来数字个数不变即可。

这道题卡了常,不用scanf会少30分 

赛时代码

#include<bits/stdc++.h>
using namespace std;
long long n,m,q,x,a[1000005],b[1000005];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>x;
		a[x]++;
	}
	for(int i=1;i<=m;i++){
		b[i]=b[i-1]/2+a[i];
	}
	cin>>q;
	while(q--){
		cin>>x;
		cout<<b[x]<<endl;
	}
	return 0;
}

正解代码

#include<bits/stdc++.h>
using namespace std;
long long n,m,q,x,a[1000005],b[1000005];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>x;
		a[x]++;
	}
	for(int i=1;i<=m;i++){
		b[i]=b[i-1]/2+a[i];
	}
	cin>>q;
	while(q--){
		scanf("%d",&x);
		printf("%d\n",b[x]);
	}
	return 0;
}

 T3 矩阵(matrix)

得分情况

赛时10pts。

题目大意

给你一个矩阵,求该矩阵每个子矩阵的异或和的总和。

解题思路

维护二维异或前缀和数组,之后枚举子矩阵的左上角和右下角,计算区间异或值即可,时间复杂度为O(n^{4})。

赛时代码

(骗分代码不展示了吧)

#include<bits/stdc++.h>
using namespace std;
int n,m;
long long ans=0,tj=0;
int a[305][305];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
			tj+=a[i][j];
		}
	}

	if(tj==n*m){
		for(int i=1;i<=n;i+=2){
			for(int j=1;j<=m;j+=2){
				ans+=(n-i+1)*(m-j+1);
			}
		}
	}else
	{
		ans=a[1][1];
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				for(int heng=0;heng<=n-i;heng++){
					for(int zong=0;zong<=m-j;zong++){
						long long anss=a[i][j];
						for(int x=i;x<=i+heng+1;x++){
							for(int y=j;y<=j+zong+1;y++){
								if(x!=i&&y!=j){
									anss^=a[x][y];
								}
							}
						}
						ans+=anss;
					}
				}
			}
		}
	}
	cout<<ans;
	return 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++){
                int cnt0=1;
                int cnt1=0;
                for(int k=1; k<=m; k++){
                    if(xo[k] & (1<<p)){
                        ans+=1ll*(1<<p)*cnt0;
                        cnt1++;
                    }
                    else{
                        ans+=1ll*(1<<p)*cnt1;
                        cnt0++;
                    }
                }
            }
        }
    }
    cout << ans;
    return 0;
}

 T4 数对(pair)

得分情况

比赛时30pts。

题目大意

给你两个数组,这两个数组有 n * m 个和,这些和在 mod q 后放进数组 c 里。问 c 中有几个逆序对。

解题思路

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

结果可能很大,需要_int128或高精压位。

赛时代码

#include<bits/stdc++.h>//归并求逆序对
using namespace std;
long long a[200000],b[200000],c[2000010],d[2000010],cnt=0;
void h(int l1,int r1,int l2,int r2){
	int t=0,i=l1,j=l2;
	while(i<=r1&&j<=r2){
		if(d[i]<=d[j])c[++t]=d[i++];
		else c[++t]=d[j++],cnt+=j-t-l1;
	}
	while(i<=r1){
		c[++t]=d[i++];
	}
	while(j<=r2){
		c[++t]=d[j++];
	}
	for(int i=1;i<=t;i++){
		d[l1+i-1]=c[i];
	}
}
void as(int l,int r){
	if(l==r)return;
	 int mid=(l+r)/2;
	 as(l,mid);
	 as(mid+1,r);
	 h(l,mid,mid+1,r);
}
int main(){
	int n,m,p;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>b[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			d[(i-1)*m+j]=(a[i]+b[j])%p;
		}
	}
	as(1,n*m);
	cout<<cnt;
    return 0;
}

正解代码

#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]]++;
    }
    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++){//k=b[(i+j)%p]+1;
                nixu[j] += num[k];
            }
            num[b[i]]++;//num[(i+j)%p]++;
            b[i]=(b[i]+1)%p;
        }
    }
    memset(num, 0, sizeof num);
    //ll ans=0;
    for(int i=1; i<=n; i++){
        //ans+=nixu[a[i]];
        jia(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];
                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;
}

四、总结

注意细节,能优化的地方尽量优化,勤于思考,能拿到的分不要丢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值