目录
一.题目报告
赛中一题AC,二题70分,三题0分,四题20分,赛后
二.赛中概况
第一题模拟秒了,第二题打表忘用scanf,printf,70分,第三题调试了很久,最终没有写出来,第四题暴力20分。
三.解题报告
T1.三个(three)
题目情况
赛中AC。
题目大意
有A,B,C三种微生物各一个,A 类微生物每一秒会繁殖出1个A类微生物,1个B类微生物,1个C类微生物。
B类微生物每一秒会繁殖出2个A类微生物,2 个C类微生物。
C类微生物每一秒会繁殖出1个A类微生物,1个B类微生物。
求n秒后A类,B类,C类微生物各是奇数还是偶数个。
题目解析
赛中思路:数据范围不大,模拟即可。
赛后正解:1.模拟 2.找规律,每三秒一循环,秒数取余3,余数为1则为奇奇偶,2为偶偶奇,0为奇奇奇。
题目正解
模拟:
#include<bits/stdc++.h>
using namespace std;
long long n,a=1,b=1,c=1,d,e,f;
int main(){
freopen("three.in","r",stdin);
freopen("three.out","w",stdout);
cin>>n;
for(int i=0;i<n;i++){
d=e=f=0;
d=a+2*b+c;
e=a+c;
f=a+2*b;
a+=d;
b+=e;
c+=f;
}
if(a%2!=0){
cout<<"odd"<<endl;
}
else{
cout<<"even"<<endl;
}
if(b%2!=0){
cout<<"odd"<<endl;
}
else{
cout<<"even"<<endl;
}
if(c%2!=0){
cout<<"odd"<<endl;
}
else{
cout<<"even"<<endl;
}
fclose(stdin);
fclose(stdout);
return 0;
}
T2.合体(fit)
题目情况
赛中70,赛后AC。
题目大意
一个长度为n的序列,可以将两个相同的数x合成为一个数x+1,求最后可以合成几个q。(多测)
题目解析
赛中思路:单层循环,用桶,a[i]+=a[i-1]/2打表。
赛后正解:打表(使用scanf,printf)。
题目正解
#include<bits/stdc++.h>
using namespace std;
long long n,a[1000001],b[1000001],m,q,x;
int main(){
freopen("fit.in","r",stdin);
freopen("fit.out","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=0;i<n;i++){
scanf("%lld",&a[i]);
b[a[i]]++;
}
for(int i=2;i<=m;i++){
b[i]+=b[i-1]/2;
}
cin>>q;
while(q--){
scanf("%lld",&x);
printf("%lld\n",d[x]);
}
fclose(stdin);
fclose(stdout);
return 0;
}
T3.矩阵(matrix)
题目情况
赛中0,赛后AC。
题目大意
一个 n行m 列的矩阵,矩阵上每个格子有一个整数,其中第i行第j列对应的格子上的整数为 gi,j。
现在定义该矩阵的一个子矩阵的快乐值为该子矩阵上的所有数字的异或和。
现在问你,该矩阵的所有子矩阵的快乐值之和为多少?
题目解析
赛中思路:新开一个数组作为二位前缀和数组,异或后进行运算,没做出来。
赛后正解:考虑将二维数组压成一维的;
枚举起始行 i 和终止行 j ,这个范围内的每一列都求异或值 x[k] ;即 x[k] 为 a[i][k] ~ a[j][k] 的异或值。
之后再对于x数组,求前缀异或值,然后枚举其左右端点,计算区间异或值即可。计算过程按位去考虑,对于某个区间,按位异或之后的值,的某一位,是否为1。只需要考虑这个区间内的这一位的1的个数是奇数个还是偶数个。
题目正解
//子矩阵异或,二维前缀和做法求二维异或和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;
}
T4.数对(pair)
题目情况
暴力20分。
题目大意
一个长度为n的数列a,一个长度为m的数列b,创建一个长度为n*m的数列c,输入一个p,满足c(i−1)×m+j=(ai+bj) mod p,求c中有多少个数对 (i,j)(i,j) 满足i<j且ci>cj?
题目解析
赛中思路:暴力模拟拿部分分。
赛后正解:将c数组拆分为n块,每块有m个数字,然后我们求逆序对可以块内求,然后再块与块之间求。
对于块内:观察到值域非常小,我们可以对 b 数组记录数字出现次数num。枚举到当前数字x,计算逆序数时,可以枚举比x大的数字的出现次数即可。考虑b后续需要变化(+a[j]) 。所有我们记录一个数组nixu[k]。表示为对于b数组所有数组都加k之后,逆序数为多少。
对于块与块:我们可以记录之前所有数字的出现次数,当前块一定是在之前块的后面,所以直接枚举值域,统计逆序数即可。
题目正解
压位高精
#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;
}
四.总结
第一题做得还可以,第二题没有注意用scanf,第三题浪费时间较多,还要掌握时间分配,继续练习。