2020年第十一届蓝桥杯大赛软件类决赛C/C++大学A组真题

发布时间 2023-06-08 18:16:55作者: 空気力学の詩

Preface

后天就要比国赛了,这次才堪堪写了三年的题

感觉这场的题就是给人一种很难受的感觉,填空题多得要死,而且皮亚诺曲线的那个说实话挺麻烦的

然后还有个极其傻逼的大模拟题(出租车),导致可做题数量很少

不过这场的压轴是个很经典的题,而且正好最近学校数学专题出到了一模一样的题目,然后传统艺能线段树也是一眼题,所以可能正式打的话结果会不错?

不管怎么样希望后天RP++吧,争取混个国一水点综测加分的说


合数个数

枚举即可,答案为1713

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
inline bool is_prime(CI x)
{
	for (RI i=2;i*i<=x;++i) if (x%i==0) return 0; return 1;
}
int main()
{
	freopen("CODE.out","w",stdout);
	int ans=0; for (RI i=2;i<=2020;++i)
	if (!is_prime(i)) ++ans; return printf("%d",ans),0;
}

含2天数

还是枚举题,注意闰年,答案为1994240

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int mouth[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int ans;
int main()
{
	freopen("CODE.out","w",stdout);
	auto fix=[&](CI y,CI m)
	{
		if (m!=2) return false;
		return y%400==0||(y%4==0&&y%100!=0);
	};
	auto calc=[&](int x)
	{
		while (x) { if (x%10==2) return 1; x/=10; } return 0;
	};
	for (RI y=1900,m,d;y<=9999;++y)
	for (m=1;m<=12;++m) for (d=1;d<=mouth[m]+fix(y,m);++d)
	if (calc(y)||calc(m)||calc(d)) ++ans;
	return printf("%d",ans),0;
}

本质上升序列

考虑暴力DP,用\(f_i\)表示以\(i\)为结尾的本质不同的LIS个数

由于要求本质不同,所以在处理完\(s_i\)后要把所有\(s_j=s_i,j<i\)\(f_j\)变为\(0\)

总复杂度\(O(n^2)\),答案为3616159

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200;
const char s[N+1]="tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf\
iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij\
gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad\
vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhewl";
long long f[N+1],ans;
int main()
{
	freopen("CODE.out","w",stdout);
	RI i,j; for (i=0;i<N;++i) for (f[i]=1,j=0;j<i;++j)
	if (s[j]<s[i]) f[i]+=f[j]; else if (s[j]==s[i]) f[j]=0;
	for (i=0;i<N;++i) ans+=f[i]; return printf("%lld",ans),0;
}

咫尺天涯

感觉算是做过的所有蓝桥杯的代码填空题中最难的一个了,不过写了这题后面另一个皮亚诺曲线的题就不用写了

大致的思路就是递推答案,考虑如果已经求出\(k-1\)阶的答案为\(F(k-1)\),则\(k\)阶的答案可以表示为拆分成\(3\times 3\)个小的区域,其中每个区域都是一个\(k-1\)阶皮亚诺曲线

每个\(k-1\)阶的答案已经知道了,然后考虑这些块之间产生的新的答案,不难递推出\(F(k)\)

计算新的影响的话由于数据范围直接枚举会产生贡献的点即可,但直接求两点间的皮亚诺曲线距离不是很方便

所以我们可以写一个函数用来求某个坐标到原点的皮亚诺曲线长度,由于这个就是F题的核心了,所以就放在后面在讲

然后放着耐心跑一会就可以得到答案184731576397539360

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=13;
int pw[N],ans;
inline int calc(CI k,int x,int y)
{
	if (!k) return 0;
	int shift=x/pw[k]*3,type=shift==3;
	if (type) shift+=3-y/pw[k]-1; else shift+=y/pw[k];
	if (shift%2==1) x=pw[k]-x%pw[k]-1;
	if (type) return (shift+1)*pw[k]*pw[k]-calc(k-1,x%pw[k],y%pw[k])-1;
	else return shift*pw[k]*pw[k]+calc(k-1,x%pw[k],y%pw[k]);
}
signed main()
{
	freopen("CODE.out","w",stdout);
	RI i,j; for (pw[1]=1,i=2;i<=12;++i) pw[i]=3LL*pw[i-1];
	for (i=1;i<=12;++i)
	{
		int ret=0; for (j=0;j<pw[i+1];++j)
		{
			ret+=abs(calc(i,j,pw[i])-calc(i,j,pw[i]-1));
			ret+=abs(calc(i,pw[i],j)-calc(i,pw[i]-1,j));
		}
		ans=9LL*ans+2LL*ret;
	}
	return printf("%lld",ans),0;
}

玩具蛇

比D题简单到不知道哪里去了,直接爆搜即可,答案552

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=5,dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
int ans; bool vis[N][N];
inline void DFS(CI x,CI y,CI cur)
{
	if (cur==16) return (void)(++ans); vis[x][y]=1;
	for (RI i=0;i<4;++i)
	{
		int nx=x+dx[i],ny=y+dy[i];
		if (nx<1||nx>4||ny<1||ny>4) continue;
		if (!vis[nx][ny]) DFS(nx,ny,cur+1);
	}
	vis[x][y]=0;
}
int main()
{
	freopen("CODE.out","w",stdout);
	for (RI i=1;i<=4;++i) for (RI j=1;j<=4;++j) DFS(i,j,1);
	return printf("%d",ans),0;
}

皮亚诺曲线距离

主要就是要实现某点到原点的距离,这个显然可以递归实现

就是通过一些判断求出这个位置是在那个\(k-1\)阶皮亚诺曲线中,然后看图发现有些位置要反着算贡献,有些位置要正着算,简单推导一下即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=40;
int pw[N],k,x1,y1,x2,y2;
inline int calc(CI k,int x,int y)
{
	if (!k) return 0;
	int shift=x/pw[k]*3,type=shift==3;
	if (type) shift+=3-y/pw[k]-1; else shift+=y/pw[k];
	if (shift%2==1) x=pw[k]-x%pw[k]-1;
	if (type) return (shift+1)*pw[k]*pw[k]-calc(k-1,x%pw[k],y%pw[k])-1;
	else return shift*pw[k]*pw[k]+calc(k-1,x%pw[k],y%pw[k]);
}
signed main()
{
	scanf("%lld%lld%lld%lld%lld",&k,&x1,&y1,&x2,&y2); k=min(k,39LL);
	RI i,j; for (pw[1]=1,i=2;i<=k;++i) pw[i]=3LL*pw[i-1];
	return printf("%lld",abs(calc(k,x1,y1)-calc(k,x2,y2))),0;
}

出租车

额对于这个题我只能说如果考场上看到了还是绕的远远的吧,稍微YY了下感觉建图细节有亿点点多

有这个时间不去把后面分值高的题目多拿点部分分啥的,我认为是不明智的

除非你能光速切完其它的所有题然后回来慢悠悠的写,否则还是不建议开的

其实说白了就是一个模拟建图然后跑最短路的题目,但由于涉及到等红绿灯这个东西细节爆炸,完全不想写了


答疑

你看出租车和这个傻逼题是一个分值的,所以比赛的时候千万不要被题目顺序搞了,毕竟4小时10题还要对拍,策略还是很重要的

这题没啥好说的,最经典的贪心问题,把所有人按照需要的总时间之和从小到大排序后模拟一遍即可

证明的话就是经典的接水模型,通过证明此时交换任意两个人都不会导致答案变优

复杂度\(O(n\log n)\),也不知道给个\(n=1000\)的范围是不是给不会调用库函数的人手写\(O(n^2)\)排序准备的

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
struct Data
{
	int s,a,e;
	inline Data(CI S=0,CI A=0,CI E=0)
	{
		s=S; a=A; e=E;
	}
	friend inline bool operator < (const Data& A,const Data& B)
	{
		return A.s+A.a+A.e<B.s+B.a+B.e;
	}
}a[N]; int n; long long ans,lst;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d%d",&a[i].s,&a[i].a,&a[i].e);
	for (sort(a+1,a+n+1),i=1;i<=n;++i) ans+=lst+a[i].s+a[i].a,lst+=a[i].s+a[i].a+a[i].e;
	return printf("%lld",ans),0;
}

奇偶覆盖

典中典的题,感觉和21年的括号序列以及22年的那个主席树比起来差的不是一点半点

首先二维平面上的矩形覆盖,一眼扫描线,并把覆盖奇数次看作\(1\),覆盖偶数次看作\(0\),此时问题转化为区间\(0/1\)反转与求区间的\(0/1\)个数

很显然直接用线段树维护区间\(0/1\)个数,然后维护个反转次数的标记,如果下传的时候标记是奇数就交换\(0/1\)个数

但是这样直接做会有一个问题就是会把未被覆盖的位置也统计上去,那也好办

我们额外维护下未被覆盖的位置的个数然后减去即可,具体实现的话由于扫描线维护区间内\(0\)的个数可以转化成区间最小值的出现次数(因为区间\(+1\)操作永远在\(-1\)之前,因此不会有负数)

那么用一个二元组\((x,y)\)表示区间最小值为\(x\)\(x\)出现的次数为\(y\),在pushup的时候更新即可

最后还有一个细节就是这题的数据范围需要离散化,而离散化的扫描线就不适合直接维护闭区间了

因为这样叶子节点的权值会没有定义,并且合并两个区间的时候中间的\(mid\)\(mid+1\)间的贡献就计算不清

处理方法也很简单,把线段树上区间统一改为左闭右开即可,具体实现可以看代码

总复杂度\(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<utility>
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=200005;
struct Data
{
	int x,l,r,opt;
	inline Data(CI X=0,CI L=0,CI R=0,CI OPT=0)
	{
		x=X; l=L; r=R; opt=OPT;
	}
	friend inline bool operator < (const Data& A,const Data& B)
	{
		return A.x<B.x;
	}
}x[N]; int n,cnt,rsty[N],cnty,x_1,y_1,x_2,y_2; long long ans[2];
class Segment_Tree
{
	private:
		struct segment
		{
			pi num,val; int tag;
		}node[N<<2];
		#define N(x) node[x].num
		#define V(x) node[x].val
		#define T(x) node[x].tag
		#define L(x) node[x].len
		#define TN CI now=1,CI l=1,CI r=cnty-1
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void apply(CI now,CI mv)
		{
			if (mv&1) swap(N(now).fi,N(now).se); V(now).fi+=mv; T(now)+=mv;
		}
		inline void pushup(CI now)
		{
			N(now).fi=N(now<<1).fi+N(now<<1|1).fi;
			N(now).se=N(now<<1).se+N(now<<1|1).se;
			if (V(now<<1).fi<V(now<<1|1).fi) V(now)=V(now<<1); else
			if (V(now<<1).fi>V(now<<1|1).fi) V(now)=V(now<<1|1); else
			V(now)=pi(V(now<<1).fi,V(now<<1).se+V(now<<1|1).se);
		}
		inline void pushdown(CI now)
		{
			if (T(now)) apply(now<<1,T(now)),apply(now<<1|1,T(now)),T(now)=0;
		}
	public:
		inline void build(TN)
		{
			N(now).fi=V(now).se=rsty[r+1]-rsty[l]; if (l==r) return;
			int mid=l+r>>1; build(LS); build(RS); pushup(now);
		}
		inline void modify(CI beg,CI end,CI mv,TN)
		{
			if (beg<=l&&r<=end) return apply(now,mv); int mid=l+r>>1; pushdown(now);
			if (beg<=mid) modify(beg,end,mv,LS); if (end>mid) modify(beg,end,mv,RS); pushup(now);
		}
		inline pi query(void)
		{
			pi tmp=N(1); if (!V(1).fi) tmp.fi-=V(1).se; return tmp;
		}
		#undef N
		#undef V
		#undef T
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i)
	{
		scanf("%d%d%d%d",&x_1,&y_1,&x_2,&y_2);
		x[++cnt]=Data(x_1,y_1,y_2,1); x[++cnt]=Data(x_2,y_1,y_2,-1);
		rsty[++cnty]=y_1; rsty[++cnty]=y_2;
	}
	sort(rsty+1,rsty+cnty+1); cnty=unique(rsty+1,rsty+cnty+1)-rsty-1;
	for (i=1;i<=cnt;++i) 
	{
		x[i].l=lower_bound(rsty+1,rsty+cnty+1,x[i].l)-rsty;
		x[i].r=lower_bound(rsty+1,rsty+cnty+1,x[i].r)-rsty;
	}
	for (SEG.build(),sort(x+1,x+cnt+1),x[cnt+1].x=x[cnt].x,i=1;i<=cnt;++i)
	{
		SEG.modify(x[i].l,x[i].r-1,x[i].opt);
		pi tmp=SEG.query(); int dlt=x[i+1].x-x[i].x;
		ans[0]+=1LL*tmp.fi*dlt; ans[1]+=1LL*tmp.se*dlt;
	}
	return printf("%lld\n%lld",ans[1],ans[0]),0;
}

蓝跳跳

真是无比逆天的巧合,学校的数学专题刚好出了这题一模一样的版本,不过数据范围不一样而且模数是NTT模数,这题的模数属实逆天

回到题目不难发现很容易写一个暴力DP,设\(f_i\)表示当前一共跳了\(i\)步,且上一步跳的步数在\([1,p-1]\)范围内的方案数,\(g_i\)表示当前一共跳了\(i\)步,且上一步跳的步数在\([p,k]\)范围内的方案数,则显然有转移:

\[f_n=\sum_{i=1}^{p-1} f_{n-i}+g_{n-i}\\ g_n=\sum_{i=p}^k f_{n-i} \]

然后不难发现如果我们把\(g_n\)的表达式带回到第一个式子,就得到:

\[f_n=\sum_{i=1}^{p-1} f_{n-i}+\sum_{i=1}^{p-1}\sum_{j=p}^k f_{n-i-j} \]

虽然后面是两个求和符号的复合,但不难看出如果把\(i+j\)看作一体的话其实也是一个\(\sum_{t=p+1}^{t=k+p-1} c_t\times f_{n-t}\)的形式

换句话说,这是一个常系数齐次线性递推的模型,一般常见且好写的做法就是矩阵快速幂,写的好的话可以拿到这题80%的分数

正解的话有两种做法,一种是用多项式取模来优化常系数齐次线性递推模型,不过由于模数不是NTT模数(TMD甚至不是质数),所以要写MTT或者三模NTT

或者注意到这里的\(p+k\)的范围其实不大,其实可以扔掉NTT,直接暴力\(O((p+k)^2)\)进行多项式取模,总复杂度为\(O((p+k)^2\times \log L)\),可以轻松跑过

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=4005,mod=20201114;
int a,b,k,f[N],g[N],t[N],ans; long long c;
inline int sum(CI x,CI y)
{
	return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
	return x-y<0?x-y+mod:x-y;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
struct Poly
{
	int a[N];
	inline Poly(void) { memset(a,0,sizeof(a)); }
	inline int& operator [] (CI x) { return a[x]; }
	friend inline Poly operator * (Poly A,Poly B)
	{
		Poly C; RI i,j; for (i=0;i<k;++i) for (j=0;j<k;++j) C[i+j]=sum(C[i+j],1LL*A[i]*B[j]%mod);
		for (i=(k-1)*2;i>=k;C[i]=0,--i) for (j=1;j<=k;++j) C[i-j]=sum(C[i-j],1LL*C[i]*t[j]%mod);
		return C;
	}
	friend inline Poly operator ^ (Poly A,long long p)
	{
		Poly mul; mul[0]=1; for (;p;p>>=1,A=A*A) if (p&1) mul=mul*A; return mul;
	}
};
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; scanf("%d%d%lld",&a,&b,&c); k=a+b-1;
	for (f[0]=i=1;i<=k;++i)
	{
		for (j=1;j<=min(b-1,i);++j) f[i]=sum(f[i],sum(f[i-j],g[i-j]));
		for (j=b;j<=min(a,i);++j) g[i]=sum(g[i],f[i-j]);
	}
	for (i=1;i<=k;++i) f[i]=sum(f[i],g[i]);
	if (c<=k) return printf("%d",f[c]),0;
	for (i=1;i<=b-1;++i) t[i]=1;
	for (i=1;i<=b-1;++i) for (j=b;j<=a;++j) ++t[i+j];
	Poly D; D[1]=1; D=D^(c-1);
	//for (i=0;i<k;++i) printf("%d\n",D[i]);
	for (i=0;i<k;++i) ans=sum(ans,1LL*D[i]*f[i+1]%mod);
	return printf("%d",ans),0;
}

Postscript

完了突然转念一想好多板子我打的是一点不熟,上次省赛已经因为忘板子被教育了

但由于好多SB实践课期末要交报告最近也是一直在忙这些零碎的东西,稍微空出来的时间还在补《Island》的杂谈以及看《Clannad》

六级和期末考试也临近了,这个月压力很大啊的说,不过熬过去就到暑假爽训的时间了,起飞