一、得分情况
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()。
赛时代码
(骗分代码不展示了吧)
#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;
}
四、总结
注意细节,能优化的地方尽量优化,勤于思考,能拿到的分不要丢。