CSP/J
T1
非常简单的位运算拆分二进制,判断奇偶性然后拆分即可,五分钟打完过大样例。
#include<bits/stdc++.h>
using namespace std;
int read(){
char s;
int x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
int lowbit(int n){
return n&(-n);
}
int ans[60];
void work(int n){
int cnt=0;
while(n){
int use=lowbit(n);
ans[cnt++]=use;
n-=use;
}
for(int i=cnt-1;i>=0;i--){
cout<<ans[i]<<" ";
}
return;
}
int main(){
freopen("power.in","r",stdin);
freopen("power.out","w",stdout);
int n;
cin>>n;
if(n&1){
cout<<"-1"<<endl;
return 0;
}
work(n);
return 0;
}
T2
给定长度为n的数组,与获奖率w。
输出n个数字,其中第i个数字表示数组中前i个数第w%大的数字大小。
第一时间想set/nth-element,发现set忘记怎么用了一时间有点慌。
后来注意到分数是一个不大于600的非负整数,因此用权值分块统计分数出现个数扫一遍挤可。
后来发现不用分块,开个桶就行。
这道题花了较多时间。
凭借记忆写的分块让我检查了好久,所幸没出错,再拿100分。
看注释就知道当时多小心谨慎。
#include<bits/stdc++.h>
using namespace std;
int read(){
char s;
int x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
const int N=100000+5;
const int M=700;
int n,w;
int a[N];
int num;//块的个数
int len;//块的大小
int le[M],ri[M];//值域分块
int c[M];//单点记录
int tot[M]; //整块记录
void build(int n){
num=sqrt(n);
len=num;
if(num*num!=n)num+=1;
for(int i=1;i<=num;i++){
le[i]=(i-1)*len+1;
ri[i]=i*len;
}
le[1]=0,ri[num]=n;
}
void insert(int pos){//pos位置+1
c[pos]+=1;
int use=pos/len;
if(use*len<pos)use+=1;
tot[use]+=1;
// if(le[use]>pos||ri[use]<pos)cout<<"cnm"<<endl;
}
int query(int p){//第几个人的分数线
int cnt=0;
for(int i=num;i>=1;i--){
cnt+=tot[i];
if(cnt>=p){//当前块为答案区间
cnt-=tot[i];
for(int j=ri[i];j>=le[i];j--){
if(c[j]==0)continue;
cnt+=c[j];
if(cnt>=p)return j;
}
}
}
}
int main(){
freopen("live.in","r",stdin);
freopen("live.out","w",stdout);
n=read(),w=read();
build(605);
for(int i=1;i<=n;i++){
a[i]=read();
insert(a[i]);
int p=max(1,i*w/100);
//cout<<p<<" ";
//cout<<query(p)<<endl;
printf("%d ",query(p));
}
return 0;
}
T3
后缀转中缀就不会,想骗全是一个符号的也没有骗成,20->0分,代码就不放了。
T4
70分 o ( n 3 ) o(n^3) o(n3)动态规划。
注意到走法不会向左走,每次都是在一列上走一会然后去下一列。
可以预处理每列前缀和, d p [ i ] [ j ] dp[i][j] dp[i][j]表示第i列停在第j个格子的分数最大值。
当时觉得可以用优先队列之类的优化,最后也没调出来。
考场70分代码:
#include<bits/stdc++.h>
using namespace std;
int read(){
char s;
int x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
const int N=1010;
int n,m;
long long mp[N][N];
long long dp[N][N];//第i列最后停留停留j位置在分数最大值
long long pre[N][N];//第i列的前缀和
int main(){
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
memset(dp,-0x3f3f3f3f,sizeof(dp));
n=read(),m=read();
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mp[i][j]=(long long)read();
}
}
for(int j=1;j<=m;j++){//枚举列
pre[j][1]=mp[1][j];
for(int i=2;i<=n;i++){
pre[j][i]=pre[j][i-1]+mp[i][j];
}
}
for(int i=1;i<=n;i++)dp[1][i]=pre[1][i];
for(int i=2;i<=m;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++){
int l=min(j,k),r=max(j,k);
dp[i][j]=max(dp[i][j],dp[i-1][k]+pre[i][r]-pre[i][l-1]);
}
}
}
printf("%lld\n",dp[m][n]);
return 0;
}
做题过程
开题顺序是1->2->4->3
第一题很快做完之后在第二题卡了一会,然后先打了第四题70分再回来发现了权值分块做法。
第二题是思考最多的,当时还感觉可以树状数组+二分,但是按位贪心就练过发牌这一道题,加上半年没刷题,还是选择最好写的算法做。
普及考完心态非常好,中午去吃汉堡。
在餐馆里睡了个午觉,准备下午提高组重头戏。
CSP/S
T1
儒略日,一看就是大模拟。考完之后被骂的最惨的一题。
上学期学校语文课学了天干地支,当时我就写过一个计算天干地支的程序,所以看到这题心里笑开了花。
根据历法不同分三段:近似年份+微调+年内枚举
后来发现近似年份用二分更稳,可能我就因为这个预估100最后40。
写了一个半小时写完,过了大样例,感觉100稳了,看第二题。
#include<bits/stdc++.h>
using namespace std;
int read(){
char s;
int x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
int n;
int month[13]={-1,31,28,31,30,31,30,31,31,30,31,30,31};
void calc_bc(){
int ans=0;
ans=4713*365+4712/4+1;
cout<<ans<<endl;
}
void calc_02(){//577725
//1582.10.04(到这天)
int ans=1581*365+1581/4-1581/100+1581/400;
for(int i=1;i<=9;i++)ans+=month[i];
ans+=4;
cout<<ans<<endl;
}
const int bc=1721424;
const int jl=577725;
bool flag;
bool is_bc(int year){
return bool((year-1)%4==0);
}
void work_bc(int r){
int x=(4*r-3)/1460;
int day=365*x+(x-1)/4+bool(!(x==0));
for(int i=0;1;i++){
if(day+365+is_bc(4713-x)<=r){
day+=365+is_bc(4713-x);
x+=1;
}
else{
break;
}
}
for(int i=0;1;i++){
if(day>r){
day-=365+is_bc(4713-x+1);
x-=1;
}
else break;
}
//cout<<day<<endl;
int ans_year=4713-x;//确定年份
int ans_month=-1,ans_day=-1;
month[2]=28+is_bc(ans_year);
//cout<<ans_year<<endl;
for(int i=1;i<=12;i++){
if(day+month[i]<=r)day+=month[i];
else{
ans_month=i;
ans_day=r-day+1;
break;
}
}
cout<<ans_day<<" "<<ans_month<<" "<<ans_year<<" BC"<<endl;
}
bool is_01(int year){
return bool(year%4==0);
}
bool is(int year){
return bool((year%400==0)||(year%4==0&&year%100!=0));
}
void work_01(int r){//儒略日历
int x=r/365;
int day=365*x+x/4;
for(int i=0;1;i++){
if(day+365+is_01(x+1)<=r){
day+=365+is_01(x+1);
x+=1;
}
else break;
}
for(int i=0;1;i++){
if(day>r){
day-=365+is_01(x);
x-=1;
}
else break;
}
int ans_year=x+1;
// cout<<ans_year<<endl;
int ans_month=-1,ans_day=-1;
month[2]=28+is_01(ans_year);
for(int i=1;i<=12;i++){
if(day+month[i]<r){
day+=month[i];
}
else{
ans_month=i;
ans_day=r-day+1;
break;
}
}
cout<<ans_day<<" "<<ans_month<<" "<<ans_year<<endl;
}
void work_02(int r){//公元后 格里高利
int x=r/365;
int day=365*x+x/4-x/100+x/400+2;//公元x+1年1.1
// cout<<x<<" ";
for(int i=0;1;i++){
if(day+365+is(x+1)<=r){
day+=365+is(x+1);
x+=1;
}
else break;
}
for(int i=0;1;i++){
if(day>r){
day-=365+is(x);
x-=1;
}
else break;
}
int ans_year=x+1;
// cout<<ans_year<<endl;
int ans_month=-1,ans_day=-1;
month[2]=28+is(ans_year);
for(int i=1;i<=12;i++){
if(day+month[i]<r){
day+=month[i];
}
else{
ans_month=i;
ans_day=r-day+1;
break;
}
}
cout<<ans_day<<" "<<ans_month<<" "<<ans_year<<endl;
}
int main(){
freopen("julian.in","r",stdin);
freopen("julian.out","w",stdout);
//calc_bc();//1721424 对应1 1 1
n=read();
for(int i=1;i<=n;i++){
int x=read();
if(x<bc)work_bc(x);
else if(x<bc+jl)work_01(x-bc);
else work_02(x-bc);
}
return 0;
}
T2
这题才是签到题……
最难的部分是题意理解。
花了十分钟理解题目,跟着题目走就是正解。
1.根据已有动物生成饲料清单。
2.根据饲料清单与驯养指南一位一位贪心,计算总的合法动物数。
3.-已有动物数
考场代码
#include<bits/stdc++.h>
using namespace std;
long long read(){
char s;
long long x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
const int N=1e6+6;
const int C=1e8+8;
long long n,m,c,k;
long long a[N];
vector<int>p[80];
int cnt_q[80];
bool list_c[C];//饲料清单
void make_list(long long all){
int tot=0;
while(tot<k){
if((all&1)&&(p[tot].size()>=1)){//买饲料
for(int i=0;i<p[tot].size();i++){
list_c[p[tot][i]]=1;
}
}
tot++;
all>>=1;
}
return;
}
int main(){
freopen("zoo.in","r",stdin);
freopen("zoo.out","w",stdout);
n=read(),m=read(),c=read(),k=read();
long long all=0;//根据all确定饲料清单
for(int i=1;i<=n;i++){
a[i]=read();
all|=a[i];
}
for(int i=1;i<=m;i++){
int x,y;
x=read(),y=read();
p[x].push_back(y);
}
make_list(all);
// for(int i=1;i<=c;i++){
// cout<<i<<" "<<list_c[i]<<endl;
// }
int rec=0;//记录随便买的位数
for(int i=0;i<=k-1;i++){//要求这位看看有没有买对应饲料
for(int j=0;j<p[i].size();j++){
if(list_c[p[i][j]]==0){
rec--;
break;
}
}
rec++;
}
long long ans=(long long)1<<(rec);
ans-=n;
printf("%lld\n",ans);
return 0;
}
好像因为没开 u n s i g n l o n g l o n g unsign longlong unsignlonglong扣了10分……
T3
30分很好拿,直接模拟即可
后来想写一个双标记线段树估计能50分,但是怕写不对+时间不足,打完暴力就去写别的题。
#include<bits/stdc++.h>
using namespace std;
long long read(){
char s;
long long x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
const int mod=998244353;
const int N=1e5+5;
int n,m,q;
long long a[N];
struct function{
int op;//种类
vector<int>v;
int pos,val;//位置 数值
}f[N];
void work(int x){
int op=f[x].op;
if(op==1){
a[f[x].pos]+=f[x].val;
a[f[x].pos]%=mod;
}
else if(op==2){
for(int i=1;i<=n;i++)a[i]*=f[x].val,a[i]%=mod;
}
else{
for(int i=0;i<f[x].v.size();i++){
work(f[x].v[i]);
}
}
}
int main(){
freopen("call.in","r",stdin);
freopen("call.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)a[i]=read(),a[i]%=mod;
m=read();
for(int i=1;i<=m;i++){
f[i].op=read();
if(f[i].op==1){
f[i].pos=read(),f[i].val=read();
}
else if(f[i].op==2){
f[i].val=read();
}
else{
int c=read();
for(int j=1;j<=c;j++){
int x=read();
f[i].v.push_back(x);
}
}
}
q=read();
for(int i=1;i<=q;i++){
int x=read();
work(x);
}
for(int i=1;i<=n;i++){
printf("%lld ",a[i]%mod);
}
return 0;
}
T4
看上去是博弈论dp,心里慌得很。
发现n<=3的就是送分,就直接拿走25分。
#include<bits/stdc++.h>
using namespace std;
long long read(){
char s;
long long x=0,f=1;
s=getchar();
while(s<'0'||s>'9'){
if(s=='-')f=-1;
s=getchar();
}
while(s>='0'&&s<='9'){
x*=10;
x+=s-'0';
s=getchar();
}
return x*f;
}
const int N=1e6+6;
int t,n;
int a[N];
int main(){
freopen("snakes.in","r",stdin);
freopen("snakes.out","w",stdout);
t=read(),n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=t;i++){
if(i!=1){
int k=read();
for(int i=1;i<=k;i++){
int x=read(),y=read();
a[x]=y;
}
}
if(n==1||n==2){
printf("%d\n",1);
continue;
}
if(n==3){
if(a[1]+a[2]<=a[3])printf("%d\n",1);
else printf("%d\n",3);
}
if(3<n){
cout<<n<<endl;
}
}
return 0;
}
文件名第一次写成snake,还好后来检查出来了,避免了这种无谓错误。
赛后
普及组洛谷估分:100+100+0+70=270 一等稳了
提高组洛谷估分:40+90+20+25=175 应该在一等奖线左右
说实话我没想过能考这么好,这次考试之前半年时间里我几乎没有刷题。
回想一年前CSP2019,我暑假每天10h做题,付出了几乎整个暑假和上半学期所有空闲时间,因为第一次参加高中组比赛,因为过于紧张签到题仅仅得了30分,第一天考完心情非常抑郁,第二天暴力几乎拿满也难以弥补第一题的失误,最后只拿了省二。
再往前的一年的普及组也是如此,第一次参赛,代码功底不深,一个简单的模拟错误百出,时间分配不合理,后来发现那道简单的模拟题如果满分(其实并不难),后面的题目不看都是一等。
这两次考试告诉我,心态和考试策略有多重要。这可能是我今年发挥不错的原因。
今年很多人都因为第一题的复杂模拟导致心态崩溃或者时间分配不合理,这次题目其实不是很难,没有非常难的动态规划或者要运用复杂数据结构,因此冷静思考就显得更加重要。
感谢有这么一天,能让我把近三年在信息竞赛中的所学所感所悟毫无保留地运用出来,弥补前两年的遗憾。
update on 2020/11/17
今天官方成绩出来了 和估分相差不大
但是普及第二题只得了60分(分块写错了?) 最终普及:100+60+0+70=230……略有遗憾
提高第三题暴力纯模拟没想到有35分
提高最终成绩:40+95+35+20=190分 与估分相差不大