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

Preface

JB学校不给搞线下考点,只能线上考麻烦地一比,不过有自己键盘可以用也不错的说,代码什么的也有存着

但今年CA的题目是真的有点搞,做了前两年的题目我以为蓝桥杯就是个DP杯罢了,最多考点线段树之类的顶天了

结果今天比赛发现变成模板杯了,然而我考场上都想得到做法就是忘了板子

不过如果写写暴力,有几题没对拍的别写挂应该还能混个省一吧

数据现在没放出来,没法测就先扔个考场代码吧,除了两个填空答案能保证对其它都不敢打包票的说


试题 A: 幸运数

暴力即可,答案应该是4430091

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int ans,c[20];
int main()
{
	freopen("A.out","w",stdout);
	RI i,j,k; for (i=1;i<=100000000;++i)
	{
		for (j=0;j<10;++j) c[j]=0; j=0;
		int x=i; while (x) c[++j]=x%10,x/=10;
		if (j&1) continue; int s1=0,s2=0;
		for (k=1;k<=j/2;++k) s1+=c[k];
		for (k=j/2+1;k<=j;++k) s2+=c[k];
		if (s1==s2) ++ans;
	}
	return printf("%d",ans),0;
}

试题 B: 有奖问答

这题好像很多人没考虑中间直接结束的情况,去知乎上看了一圈这个8335366应该没什么问题的

也没什么技巧直接暴力枚举的说

#include<cstdio>
#include<iostream>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=1<<30;
int ans; set <int> s;
int main()
{
	freopen("B.out","w",stdout);
	for (RI i=0,j;i<N;++i)
	{
		int cur=0;
		for (j=0;j<30;++j)
		{
			if (i&(1<<j))
			{
				if ((cur+=10)==100) break;
			} else cur=0;
			if (cur==70)
			{
				int pre=i&((1<<j+1)-1);
				if (!s.count(pre))
				{
					++ans; s.insert(pre);
					//printf("Case %d:\n",ans);
					//for (RI k=0;k<=j;++k) printf("%d ",(i>>k)&1);
					//putchar('\n');
				}
			}
		}
	}
	return printf("%d\n",ans),0;
}

试题 C: 平方差

经典问题了属于是,不难发现所有奇数和\(4\)的倍数都是合法的,直接算一下就好了

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int l,r;
inline int calc(CI x)
{
	if (x<2) return x; return x-((x-2)/4+1);
}
int main()
{
	freopen("C.in","r",stdin); freopen("C.out","w",stdout);
	return scanf("%d%d",&l,&r),printf("%d",calc(r)-calc(l-1)),0;
}

试题 D: 更小的数

本场唯一的DP题了,但实在是太简单没什么技术含量的说

很容易想到考虑设状态\(f_{l,r}\)表示翻转\([l,r]\)后能否比原来小,则显然有转移

\[f_{l,r}=(a_l>a_r)\or(a_l=a_r\and f_{l+1,r-1}) \]

枚举的顺序就按照区间大小走就行了,总复杂度\(O(n^2)\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005;
int n,ans; char a[N]; bool f[N][N];
int main()
{
	freopen("D.in","r",stdin); freopen("D.out","w",stdout);
	RI i,j; scanf("%s",a+1); n=strlen(a+1);
	for (j=1;j<=n;++j) for (i=1;i+j-1<=n;++i)
	f[i][i+j-1]=(a[i]>a[i+j-1])||(a[i]==a[i+j-1]&&f[i+1][i+j-2]);
	for (i=1;i<=n;++i) for (j=i;j<=n;++j) ans+=f[i][j];
	return printf("%d",ans),0;
}

试题 E: 颜色平衡树

Dsu on tree战俘闪总出列!

考场上最后半小时对着三四行代码没调出来,赛后看一眼博客看了下板子一眼改过拍上了

首先这种静态统计子树信息的一眼Dsu on tree,然后考虑用经典的数颜色的策略把出现过的颜色的次数都扔在一个multiset里,然后检验的话就看最大和最小元素是否相等即可

但这样直接维护好像复杂度是\(O(n\log^2 n)\)的,感觉也不知道能不能跑过

#include<cstdio>
#include<iostream>
#include<vector>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,c[N],bkt[N],x,ans,sz[N],son[N]; vector <int> v[N]; multiset <int> s;
inline void DFS(CI now=1)
{
	sz[now]=1; for (int to:v[now])
	if (DFS(to),sz[now]+=sz[to],sz[to]>sz[son[now]]) son[now]=to;
}
inline void calc()
{
	multiset <int>::iterator it=s.end();
	if (*s.begin()==*(--it)) ++ans;
}
inline void add(CI x)
{
	if (bkt[x]>0) s.erase(s.find(bkt[x])); s.insert(++bkt[x]);
}
inline void del(CI x)
{
	s.erase(s.find(bkt[x])); if (--bkt[x]>0) s.insert(bkt[x]);
}
inline void travel_add(CI now)
{
	for (int to:v[now]) travel_add(to); add(c[now]);
}
inline void travel_del(CI now)
{
	for (int to:v[now]) travel_del(to); del(c[now]);
}
inline void DSU(CI now=1)
{
	for (int to:v[now]) if (to!=son[now]) DSU(to),travel_del(to);
	if (son[now]) DSU(son[now]); add(c[now]); calc();
	for (int to:v[now]) if (to!=son[now]) travel_add(to);
}
int main()
{
	freopen("E.in","r",stdin); freopen("E.out","w",stdout);
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d%d",&c[i],&x),v[x].push_back(i);
	return DFS(),DSU(),printf("%d",ans),0;
}

但后来想了下感觉可以直接线段树合并解决啊,动态开点记录每个颜色出现次数然后启发式合并,不过复杂度好像还是\(O(n\log^2 n)\)

Upt:看知乎上说有用unordered_map维护的一个\(\log\)的做法,但没仔细看怎么做

代码就懒得写了,坑


试题 F: 买瓜

数据范围一眼Meet in middle,然后一写确实是这样的

首先为了避免小数的问题,我们可以把\(m\)\(2\),然后默认每个数\(a_i\)都乘\(2\),如果要劈开就直接选\(a_i\)

显然我们爆搜左右两个部分得到每种和对应的最少操作次数,然后合并的过程也是非常trivial的

正常情况下复杂度就是\(O(3^{\frac{n}{2}})\)的,不过之前用了map就慢的一批,换成unordered_map后本机不开O2大概1.8s,希望能过

#include<cstdio>
#include<iostream>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define fi first
#define se second
using namespace std;
const int N=35,INF=1e9;
int n,m,a[N],ans=INF; unordered_map <int,int> A,B;
inline void DFS(CI now,CI lim,unordered_map <int,int>& f)
{
	if (now>lim) return; unordered_map <int,int> g=f;
	for (auto it:f)
	{
		int tmp=it.fi+a[now]*2;
		if (tmp<=m&&(!g.count(tmp)||g[tmp]>it.se)) g[tmp]=it.se;
		tmp=it.fi+a[now];
		if (tmp<=m&&(!g.count(tmp)||g[tmp]>it.se+1)) g[tmp]=it.se+1;
	}
	f=g; DFS(now+1,lim,f);
}
signed main()
{
	freopen("F.in","r",stdin); freopen("F.out","w",stdout);
	RI i; for (scanf("%lld%lld",&n,&m),m<<=1,i=1;i<=n;++i) scanf("%lld",&a[i]);
	A[0]=0; DFS(1,n>>1,A); B[0]=0; DFS((n>>1)+1,n,B);
	/*printf("elements in A are:\n");
	for (auto it:A) printf("%d %d\n",it.fi,it.se);
	printf("elements in B are:\n");
	for (auto it:B) printf("%d %d\n",it.fi,it.se);*/
	for (auto it:A) if (B.count(m-it.fi)) ans=min(ans,it.se+B[m-it.fi]);
	return printf("%lld",ans!=INF?ans:-1),0;
}

试题 G: 网络稳定性

寄了今天突然心血来潮看了眼代码发现存边的数组开小了,\(3\times 10^5\)开成\(10^5\)了,白丟\(40\%\)的分苦路西

这题也是经典模板了,从大到小建出Kruskal重构树后,问题就变成了求树上两点间边权的最小值

直接用倍增在求LCA的过程中处理一下即可,总复杂度\(O(n\log n)\)

(woc妈的数组怎么会开小的啊,受不了了心态崩了,这里贴的代码补上了)

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<algorithm>
#define RI register int
#define CI const int&
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair <int,int> pi;
const int N=100005,P=20;
struct edge
{
	int x,y,w;
	friend inline bool operator < (const edge& A,const edge& B)
	{
		return A.w>B.w;
	}
}e[N*3]; int n,m,q,x,y,fa[N]; vector <pi> v[N]; bool vis[N];
inline int getfa(CI x)
{
	return x!=fa[x]?fa[x]=getfa(fa[x]):x;
}
namespace T
{
	int dep[N],anc[N][P],mi[N][P];
	inline void DFS(CI now,CI fa=0)
	{
		dep[now]=dep[fa]+1; vis[now]=1;
		for (RI i=0;i<P-1;++i) if (anc[now][i])
		{
			anc[now][i+1]=anc[anc[now][i]][i];
			mi[now][i+1]=min(mi[now][i],mi[anc[now][i]][i]);
		} else break;
		for (auto to:v[now]) if (to.fi!=fa)
		anc[to.fi][0]=now,mi[to.fi][0]=to.se,DFS(to.fi,now);
	}
	inline int getmin(int x,int y)
	{
		if (dep[x]<dep[y]) swap(x,y); RI i; int ret=1e9;
		for (i=P-1;~i;--i) if (dep[anc[x][i]]>=dep[y])
		ret=min(ret,mi[x][i]),x=anc[x][i];
		if (x==y) return ret;
		for (i=P-1;~i;--i) if (anc[x][i]!=anc[y][i])
		ret=min(ret,min(mi[x][i],mi[y][i])),x=anc[x][i],y=anc[y][i];
		return min(ret,min(mi[x][0],mi[y][0]));
	}
};
int main()
{
	freopen("G.in","r",stdin); freopen("G.out","w",stdout);
	RI i; for (scanf("%d%d%d",&n,&m,&q),i=1;i<=n;++i) fa[i]=i;
	for (i=1;i<=m;++i) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].w);
	for (sort(e+1,e+m+1),i=1;i<=m;++i)
	{
		int fx=getfa(e[i].x),fy=getfa(e[i].y);
		if (fx==fy) continue; fa[fx]=fy;
		v[e[i].x].pb(mp(e[i].y,e[i].w)); v[e[i].y].pb(mp(e[i].x,e[i].w));
	}
	for (i=1;i<=n;++i) if (!vis[i]) T::DFS(i);
	for (i=1;i<=q;++i)
	{
		scanf("%d%d",&x,&y);
		if (getfa(x)!=getfa(y)) puts("-1");
		else printf("%d\n",T::getmin(x,y));
	}
	return 0;
}

试题 H: 异或和之和

纯送分题,那么经典的按位讨论,感觉比EFG都简单

首先我们按位考虑每一个二进制位,然后枚举右端点,考虑计算有多少个左端点是合乎要求的

不难发现前面符合要求的点一定是某些\(1\)和它前面一段连续的\(0\)组成,而具体是哪些\(1\)产生贡献只要看出现的次序是奇数还是偶数即可

总之就是一个放在CF Div2的C题都嫌简单的题,随便做做就行

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,P=21;
int n,a[N],num[N],c[2]; long long ans;
int main()
{
	freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (j=0;j<P;++j)
	{
		int idx=0,lst=0; long long ret=0;
		for (c[0]=c[1]=0,i=1;i<=n;++i)
		{
			if (!(a[i]&(1<<j))) ret+=c[num[lst]&1];
			else num[i]=++idx,c[idx&1]+=i-lst,lst=i,ret+=c[idx&1];
		}
		ans+=ret*(1LL<<j);
	}
	return printf("%lld",ans),0;
}

试题 I: 像素放置

本来看一眼以为是个高斯消元的题,结果发现方程数量不够而且值域不能保证只有\(0/1\)

然后以为是个按行来的状压DP,但感觉记录状态要存上连着的两行的信息,再加上转移的一行状态以及检验的复杂度大概是\(2^{30}\times 10\)的,不太能跑得动

然后这时候也没什么时间了就写了个\(50\%\)的暴力就不管了,样例都跑不了只能自己手造点小数据测测

结果后来一看一问直接一边爆搜一边剪枝就行了,因为是保证只有唯一解的所以跑的飞快

苦路西早知道就不偷这点懒了,我是搜完再判断的,这下感觉暴力分都有点吃紧了

后面正确的搜索也不想改了,就这样吧毁灭吧

#include<cstdio>
#include<cstdlib>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=15;
int n,m,ans[N][N]; char a[N][N];
inline void check(void)
{
	RI i,j; for (i=1;i<=n;++i) for (j=1;j<=m;++j)
	if (a[i][j]>='0'&&a[i][j]<='9')
	{
		int ret=0;
		for (RI p=-1,q;p<=1;++p) for (q=-1;q<=1;++q)
		{
			int x=i+p,y=j+q;
			if (x<1||x>n||y<1||y>m) continue;
			ret+=ans[x][y];
		}
		if (ret!=a[i][j]-'0') return;
	}
	for (i=1;i<=n;++i,putchar('\n'))
	for (j=1;j<=m;++j) putchar(ans[i][j]+'0');
	exit(0);
}
inline void DFS(CI x=1,CI y=1)
{
	if (x>n) return check(); if (y>m) return DFS(x+1,1);
	ans[x][y]=0; DFS(x,y+1); ans[x][y]=1; DFS(x,y+1);
}
int main()
{
	freopen("I.in","r",stdin); freopen("BF.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",a[i]+1);
	return DFS(),0;
}

试题 J: 翻转硬币

数论题一直是我弱项的说,但这题还好找到一个关键性质把\(70\%\)的部分分写了,不算太亏

首先一个\(O(n\ln n)\)的暴力递推的做法是很trivial的,因为我们从小到大枚举每个数,如果它是朝下的就一定要翻一次,否则后面就影响不到它了

然后我们手玩一下发现前面的数里\(4,9,12,16,18,20\)这些是不用翻的,稍加分析我们发现它们都有某个质数的平方作为因子

那么问题就很好处理了,我们考虑经典地用容斥算一下每个质因子的平方的倍数出现了几次

由于\(10^9\)范围内质数的数量才\(32000\)个左右,爆搜的复杂度看似爆炸不过随便找几个乘起来一下就超过\(10^9\)了所以还是能很快跑出来的

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=32000;
int n,ans,pri[N+5],cnt; bool vis[N+5];
inline void init(CI n)
{
	for (RI i=2,j;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		if (vis[i*pri[j]]=1,i%pri[j]==0) break;
	}
}
inline void DFS(CI now=1,CI c=0,const long long mul=1)
{
	if (mul>n) return; if (now>cnt||mul*pri[now]*pri[now]>n)
	{
		if (c&1) ans-=n/mul; else ans+=n/mul; return;
	}
	DFS(now+1,c,mul); DFS(now+1,c+1,mul*pri[now]*pri[now]);
}
/*inline int BF(CI n)
{
	static int rev[100005]; int ret=0;
	RI i,j; for (rev[1]=0,i=2;i<=n;++i) rev[i]=1;
	for (i=1;i<=n;++i) if (!rev[i])
	for (++ret,j=i;j<=n;j+=i) rev[j]^=1;
	return ret;
}*/
int main()
{
	freopen("J.in","r",stdin); freopen("J.out","w",stdout);
	return scanf("%d",&n),init(N),DFS(),printf("%d",ans),0;
	/*for (init(N),n=1;n<=100000;++n)
	if (ans=0,DFS(),ans!=BF(n)) return printf("WA on n=%d\n",n),0;*/
	return 0;
}

然后正解的话我们结合\(\mu(x)\)的定义发现其实就是要求\(\sum_{i=1}^n \mu^2(i)\),不过对于\(10^{18}\)的数据范围我也不知道怎么搞,杜教筛都搞不定的说


Postscript

大寄特寄了这下,省一不保真要没脸见人了呜呜呜,老天保佑这边竞争弱一点混个奖吧

Upt:成绩出来了感觉强运附体了,水了个省rk8可海星,不过讲真SC赛区也没啥竞争力,省一电专占80%

虽然国赛去不了北京但还是要准备下吧,写写真题找找感觉,争取多拿点综测加分的说

posted @ 2023-04-09 11:15  空気力学の詩  阅读(260)  评论(0编辑  收藏  举报