第十五届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组

Preface

答辩圈钱杯,去年省赛的题好歹还有些有意思的题(指杜教筛),今年变成煞笔题开会了是吧

两个小时多一点就全写完了,然后开始给后面三题写对拍(结果发现其实都没写挂纯在浪费时间)

考完感觉AK有望,结果后面发现最后一题漏看条件了,苦露西

而且中间EF两题全偷懒开__int128了,反正用下发的编译器开C++11是能跑的,也看的网上有其他人用了__int128,希望别CE的说


试题 A: 艺术与篮球

签到,直接枚举判断就好了,话说这种日期处理的相关题是蓝桥杯特色环节吗

答案应该是3228,但两个填空题比赛时候的代码不知道为什么被我弄没了,没法贴在这里的说


试题 B: 五子棋对弈

还是大力枚举题,但要注意白子/黑子的数量应该是\(13/12\),看到好多错的就是忘记判这个了

答案应该是3126376


试题 C: 训练士兵

刚开始以为答案关于组团训练的次数有三分性之类的,后面看了眼数据范围原来可以直接枚举组团训练的次数\(t\)

然后需要维护的就是\(c_i>t\)的士兵的\(\sum c_i\times p_i\)\(\sum p_i\)的值了,拿个后缀和随便维护一下

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,S,p,c,mx_c,spx[N],sfx[N];
signed main()
{
	RI i; for (scanf("%lld%lld",&n,&S),i=1;i<=n;++i)
	scanf("%lld%lld",&p,&c),mx_c=max(mx_c,c),spx[c]+=p,sfx[c]+=c*p;
	for (i=mx_c-1;i>=1;--i) spx[i]+=spx[i+1],sfx[i]+=sfx[i+1];
	int ans=sfx[1]; for (i=1;i<=mx_c;++i)
	ans=min(ans,i*S+(sfx[i]-spx[i]*i));
	return printf("%lld",ans),0;
}

试题 D: 团建

据说是个傻逼题,但有铸币做不来怎么办

没关系直接大力Hash就完事,对两棵树的每个根到当前点的路径做Hash,然后拿个map维护下匹配即可

写了双Hash应该也不会挂,自从用了这份模数后好像都没被卡过Hash

#include<cstdio>
#include<iostream>
#include<vector>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod1=998244353,mod2=1e9+7;
int n,m,x,y,a[N],b[N],ans; vector <int> A[N],B[N];
struct Hasher
{
	int x,y;
	inline Hasher(CI X=0,CI Y=0)
	{
		x=X; y=Y;
	}
	friend inline bool operator < (const Hasher& A,const Hasher& B)
	{
		return A.x!=B.x?A.x<B.x:A.y<B.y;
	}
	friend inline Hasher operator + (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
	}
	friend inline Hasher operator - (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
	}
	friend inline Hasher operator * (const Hasher& A,const Hasher& B)
	{
		return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
	}
}; set <Hasher> rst;
const Hasher seed=Hasher(31,131);
inline void DFS1(CI now=1,CI fa=0,Hasher pre=Hasher())
{
	pre=pre*seed+Hasher(a[now],a[now]); rst.insert(pre);
	for (auto to:A[now]) if (to!=fa) DFS1(to,now,pre);
}
inline void DFS2(CI now=1,CI fa=0,CI dep=1,Hasher pre=Hasher())
{
	pre=pre*seed+Hasher(b[now],b[now]);
	if (rst.count(pre)) ans=max(ans,dep);
	for (auto to:B[now]) if (to!=fa) DFS2(to,now,dep+1,pre);
}
int main()
{
	RI i; scanf("%d%d",&n,&m);
	for (i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<=m;++i) scanf("%d",&b[i]);
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),A[x].push_back(y),A[y].push_back(x);
	for (i=1;i<m;++i) scanf("%d%d",&x,&y),B[x].push_back(y),B[y].push_back(x);
	return DFS1(),DFS2(),printf("%d",ans),0;
}

试题 E: 成绩统计

首先一眼二分答案\(x\),考虑如何check(x)

不难发现对于\(1\sim x\)这段前缀,先将所有数排个序,然后每次选相邻的\(k\)个一定是最优的

但计算方差的时候显然直接按照定义来不好处理,我们用\(D(x)=E(X^2)-[E(x)]^2\)转化一下就很容易用前缀和处理了

但要注意把比较的时候分母全乘到分子上时会爆long long,需要开__int128

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,T,a[N],b[N]; long long pfx[N],pfx2[N];
inline bool check(CI x)
{
	RI i; for (i=1;i<=x;++i) b[i]=a[i]; sort(b+1,b+x+1);
	for (i=1;i<=x;++i) pfx[i]=pfx[i-1]+b[i],pfx2[i]=pfx2[i-1]+1LL*b[i]*b[i];
	for (i=k;i<=x;++i)
	{
		long long sum=pfx[i]-pfx[i-k],sum2=pfx2[i]-pfx2[i-k];
		if ((__int128)(sum2)*k-(__int128)(sum)*sum<(__int128)(T)*k*k) return 1;
	}
	return 0;
}
int main()
{
	RI i; for (scanf("%d%d%d",&n,&k,&T),i=1;i<=n;++i) scanf("%d",&a[i]);
	int l=k,r=n,mid,ret=-1; while (l<=r)
	if (check(mid=l+r>>1)) ret=mid,r=mid-1; else l=mid+1;
	return printf("%d",ret),0;
}

试题 F: 因数计数

感觉算是本场最难的一个题了,我当时是看一眼觉得有点烦然后直接跳了,写完后面两个一眼题后回头写的

刚开始的做法是考虑确定四元组\((i,j,k,l)\)中的前两项再计数,但感觉要讨论很多并且复杂度也不好优化

后面考虑先枚举确定\(i,k\),因为它们有倍数关系所以直接枚举复杂度是\(O(n\log n)\)的(假设\(n\)和值域同阶)

然后我们要考虑的就是当少了确定的这两个数后,剩下的有倍数关系的数对的数量

这个很容易想到容斥计算,用初始时全局的倍数关系数对的数量减去\(i,k\)各自带来的影响即可

注意需要分\(i\)是否等于\(k\)两种情况来讨论计算,代码还是很好写的

(PS:最后如果所有数都相同答案就是\(A_n^4\),会爆long long,因此又要开__int128

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,x,c[N],f[N],all; __int128 ans;
inline void write(__int128 x)
{
	if (x==0) return (void)(putchar('0'));
	if (x>9) write(x/10); putchar(x%10+'0');
}
signed main()
{
	//freopen("F.in","r",stdin); freopen("F.out","w",stdout);
	RI i,j; for (scanf("%lld",&n),i=1;i<=n;++i)
	scanf("%lld",&x),++c[x],m=max(m,x);
	for (i=1;i<=m;++i) if (c[i])
	for (j=i*2;j<=m;j+=i) if (c[j])
	f[i]+=c[j],f[j]+=c[i],all+=c[i]*c[j];
	for (i=1;i<=m;++i) all+=c[i]*(c[i]-1);
	for (i=1;i<=m;++i) if (c[i])
	{
		auto delta=[&](CI x,CI d)
		{
			return x*(x-1)-(x-d)*(x-d-1);
		};
		if (c[i]>=2) ans+=(__int128)(c[i])*(c[i]-1)*(all-delta(c[i],2)-f[i]*2);
		for (j=i*2;j<=m;j+=i) if (c[j]) ans+=(__int128)(c[i])*c[j]*(all-delta(c[i],1)-delta(c[j],1)-f[i]-f[j]+1);
	}
	return write(ans),0;
}

试题 G: 零食采购

这TM绝对是很久之前的NOIP原题了,我感觉我以前肯定做过,而且颜色数\(20\)也是很经典了

刚开始看完题以为要写树上莫队了,感觉这东西讨论起来有点小复杂,很久没写都快忘了

后面一看数据范围\(c_i\le 20\)直接乐了,直接把每种颜色状压然后合并的时候或一下即可

本来以为要写树剖的以为又要被板子题腐乳了,后面一想妈的又没有修改直接写个倍增完事

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,q,c[N],x,y,cnt[(1<<20)+5]; vector <int> v[N];
int anc[N][20],col[N][20],dep[N];
inline void DFS(CI now=1,CI fa=0)
{
	dep[now]=dep[fa]+1; anc[now][0]=fa; col[now][0]=(1<<c[now]);
	for (RI i=0;i<19;++i) if (anc[now][i])
	anc[now][i+1]=anc[anc[now][i]][i],col[now][i+1]=col[now][i]|col[anc[now][i]][i]; else break;
	for (auto to:v[now]) if (to!=fa) DFS(to,now);
}
inline int getLCA(int x,int y)
{
	RI i; if (dep[x]<dep[y]) swap(x,y);
	for (i=19;i>=0;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i];
	if (x==y) return x;
	for (i=19;i>=0;--i) if (anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
	return anc[x][0];
}
inline int getcol(int x,CI y)
{
	int ret=0; for (RI i=19;i>=0;--i)
	if (dep[anc[x][i]]>=dep[y]) ret|=col[x][i],x=anc[x][i];
	return ret|(1<<c[y]);
}
int main()
{
	//freopen("G.in","r",stdin); freopen("G.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&q),i=1;i<=n;++i) scanf("%d",&c[i]),--c[i];
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (i=1;i<(1<<20);++i) cnt[i]=cnt[i>>1]+(i&1);
	for (DFS(),i=1;i<=q;++i)
	{
		scanf("%d%d",&x,&y); int z=getLCA(x,y);
		printf("%d\n",cnt[getcol(x,z)|getcol(y,z)]);
	}
	return 0;
}

试题 H: 封印宝石

这种字典序最大/最小的题目都是一套流程,按位枚举然后贪心选,这题也不例外

从左往右枚举当前位置\(i\),考虑能放入当前盒子的宝石范围为\([i,i+k]\),那么显然从里面挑一个最大的即可

如果有多个最大的那么显然先拿靠左的一定更优,然后随便写个线段树维护下就行了

结果赛后才发现有个“相邻的两个盒子的宝石不能相同”,那就再额外维护个严格次大值以及其最左的位置即可

(PS:本题代码在赛后改过,加上了找次大值的部分,但很奇怪在民间数据的dashOJ上还是无法通过,合理推测是造的数据锅了,因为除了admin好像没一个过的)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,a[N],ans[N];
struct ifo
{
	int mx,mxp,smx,smxp;
	inline ifo(CI MX=-1e9,CI MXP=0,CI SMX=-2e9,CI SMXP=0)
	{
		mx=MX; mxp=MXP; smx=SMX; smxp=SMXP;
	}
};
class Segment_Tree
{
	private:
		ifo O[N<<2];
		inline ifo merge(const ifo& A,const ifo& B)
		{
			ifo C; if (A.mx>B.mx)
			{
				C.mx=A.mx; C.mxp=A.mxp;
				if (A.smx>=B.mx) C.smx=A.smx,C.smxp=A.smxp;
				else C.smx=B.mx,C.smxp=B.mxp;
			} else if (A.mx<B.mx)
			{
				C.mx=B.mx; C.mxp=B.mxp;
				if (A.mx>=B.smx) C.smx=A.mx,C.smxp=A.mxp;
				else C.smx=B.smx,C.smxp=B.smxp;
			} else
			{
				C.mx=A.mx; C.mxp=A.mxp;
				if (A.smx>=B.smx) C.smx=A.smx,C.smxp=A.smxp;
				else C.smx=B.smx,C.smxp=B.smxp;
			}
			return C;
		}
	public:
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			if (l==r) return (void)(O[now]=ifo(a[l],l));
			int mid=l+r>>1; build(LS); build(RS);
			O[now]=merge(O[now<<1],O[now<<1|1]);
		}
		inline ifo query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return O[now]; int mid=l+r>>1; ifo ret;
			if (beg<=mid) ret=merge(ret,query(beg,end,LS));
			if (end>mid) ret=merge(ret,query(beg,end,RS)); return ret;
		}
		inline void updata(CI pos,TN)
		{
			if (l==r) return (void)(O[now]=ifo(-1,l)); int mid=l+r>>1;
			if (pos<=mid) updata(pos,LS); else updata(pos,RS);
			O[now]=merge(O[now<<1],O[now<<1|1]);
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (SEG.build(),ans[0]=-1,i=1;i<=n;++i)
	{
		int pos=SEG.query(i,min(n,i+k)).mxp;
		if (a[pos]==-1) { ans[i]=-1; continue; }
		if (ans[i-1]==-1||a[pos]!=ans[i-1])
		{
			ans[i]=a[pos]; a[pos]=-1;
			k-=(pos-i); SEG.updata(pos);
		} else
		{
			pos=SEG.query(i,min(n,i+k)).smxp;
			if (a[pos]==-1) { ans[i]=-1; continue; }
			ans[i]=a[pos]; a[pos]=-1;
			k-=(pos-i); SEG.updata(pos);
		}
	}
	for (i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	return 0;
}

Postscript

感觉今年只要EF不要因为__int128CE还是随便拿省一的说

但不管怎么说既然来打圈钱杯了目标肯定就是国一了,去年沟槽的国二第三名真是太难受了

posted @ 2024-04-16 17:05  空気力学の詩  阅读(247)  评论(1编辑  收藏  举报