【ZKNU】2023“华杉杯”ACM校赛题解

1,873 阅读5分钟

A 一个奇妙的旅程

思路

直接输出。

代码

#include <stdio.h>
int main()
{
    printf("Have a good day!");
    return 0;
}

B 最令人高兴的旅行方案

思路

容易发现两人都喜欢的景点对答案的贡献为 22,只有一个人喜欢的景点对答案的贡献为 00,二人都不喜欢的景点对答案的共享是 2-2,所以我们只需要统计出有多少个两人都喜欢的景点 cntcnt,然后答案即为 2×cnt2\times cnt

代码

#include <stdio.h>
int n,x,y;
int cnt[200005];
int main()
{
	scanf("%d%d%d",&n,&x,&y);
	for (int a,i=1;i<=x;++i)
	{
		scanf("%d",&a);
		cnt[a]++;
	}
	for (int a,i=1;i<=y;++i)
	{
		scanf("%d",&a);
		cnt[a]++;
	}
	int ans=0;
	for (int i=1;i<=n;++i)
		if (cnt[i]==2) ans+=2;
	printf("%d\n",ans);
	return 0;
}

C 简单博弈游戏

思路

如果 NN 的个位值为 00,则先手必输,否则先手必胜。理由如下:

如果当前剩余的瓜子数的个位 tt 不是 00,就拿走tt 个瓜子。这样的话如果对手还能进行操作,他操作后剩余瓜子数量的个位一定不是 00,还可以用同样的策略进行操作。直到对手不能进行操作为止。

数据范围较小,如果找不到上述的简单规律也可以用动态规划解决该问题。

代码

#include <stdio.h>
int main()
{
    int T,n;
    for (scanf("%d",&T);T--;)
        if (scanf("%d",&n),n%10==0) printf("Wumeinv\n");
        else printf("Ice_teapoy\n");
    return 0;
}

D 以fibonacci数列排列的花

思路

奇数+奇数=偶数

奇数+偶数=奇数

所以斐波那契数列的奇偶性为 奇、奇、偶、奇、奇、偶、奇、奇、偶、奇、奇、偶……

所以本题的答案为 nn3n-\lfloor\frac{n}{3}\rfloor

代码

#include <stdio.h>
int main()
{
    int T;
    for (scanf("%d",&T);T--;)
    {
        long long n;
        scanf("%lld",&n);
        printf("%lld\n",n-n/3);
    }
    return 0;
}

E 博物馆门票

思路

小学数字题 qwq

输出 min(1.6x,max(kx×x,2x)2)min(1.6x,\frac{max(\lfloor\frac{k}{x}\rfloor\times x,2x)}{2})

容易发现买四张门票来打五折相当于不打折,所以我们最多买三张票。所以可能选择的方案有:

  1. 买两张学生票,答案可以取 1.6x1.6x
  2. 买两张成人票打五折,当 2xk2x\ge k 时,答案可以取 xx
  3. 买三张成人票打五折,当 3xk3x\ge k 时,答案可以取 1.5x1.5x

代码

#include <stdio.h>
#include <algorithm>
using namespace std;
int main()
{
	int x,k;
	scanf("%d%d",&x,&k);
    if (2*x>=k) printf("%.2lf\n",x*1.0);
    else if (3*x>=k) printf("%.2lf\n",x*1.5);
    else printf("%.2lf\n",x*1.6);
	return 0;
}

F 爬山

思路

容易发现并不会来回走……

令 l[i] 表示第 i 座山往左最远能走到哪:

  • 如果第 i 座山能去往第 i-1 座山,则 l[i]=l[i-1]+1
  • 否则 l[i]=1。

同理求出第 i 座山往右最远能走到哪。两者中的最大值即为答案。

代码

#include <stdio.h>
const int N=200005;
int n,k,a[N];
int l[N],r[N]; 
int main()
{
	scanf("%d",&n);
	scanf("%d",&k);
	for (int i=1;i<=n;++i) scanf("%d",&a[i]);
	int ans=1;
	l[1]=r[n]=1;
	for (int i=2;i<=n;++i)
		if (a[i]+k>=a[i-1]) l[i]=l[i-1]+1;
		else l[i]=1;
	for (int i=n-1;i>=1;--i)
		if (a[i]+k>=a[i+1]) r[i]=r[i+1]+1;
		else r[i]=1;
	for (int i=1;i<=n;++i)
    {
        if (ans<l[i]) ans=l[i];
        if (ans<r[i]) ans=r[i];
    } 
	printf("%d\n",ans); 
	return 0;
}

G 祈福串珠

思路

性质一

一串串制作等价于同时制作多串。

具体来说,由于蓝桥 A 梦每次都是按序号把珠子给 Ity,当某个珠子不能串在第 ii 个珠串时,Ity 可以直接试图把它串在第 i+1i+1 根绳子上,而不必把它还给蓝桥 A 梦等待下一轮制作。

性质二

所有的串串首不增串尾不减。

具体来说,记第 ii 个珠串最左边珠子的大小为 lil_i,最右边珠子的大小为 rir_i。则任一时刻有

l1l2l3...r1r2r3...l_1\ge l_2\ge l_3\ge...\\ r_1\le r_2\le r_3\le...

否则不满足制作规则。

解法

综上所述,对于第 t(1tn)t(1\le t\le n) 颗珠子大小为 ata_t

  • ll 数组中二分一个最小值 ii 满足 li>atl_i>a_t
  • rr 数组中二分一个最小值 jj 满足 rj<atr_j<a_t
  • i<ji<j,珠子 tt 应该串在第 ii 根绳子的左端,更新 lil_i;若 i>ji>j,珠子 tt 应该串在第 jj 根绳子的右端,更新 rjr_j;否则,珠子 tt 应该串在新的空绳子上。

容易发现更新不会影响数组 llrr 的单调性,思路类似于最长上升子序列的二分解法。顺便统计答案即可。


update:有群友说直接二分珠子第一个不被包含的珠串就可以了,我感觉非常对,但是代码是我的旧思路懒得改了(

代码

#include <stdio.h>
#include <algorithm>
using namespace std;
const int N=200005;
int n;
int a[N],b[N],tot,cnt[N];
int main()
{
	scanf("%d",&n);
	for (int i=1,x,aa,bb,l,r,mid;i<=n;++i)
	{
		scanf("%d",&x);
		if (tot==0)
		{
			cnt[++tot]=1;
			a[1]=b[1]=x;
			continue;
		}
		if (a[tot]>x)
		{
			for (l=1,r=tot;l!=r;)
			{
				mid=l+r>>1;
				if (a[mid]>x) r=mid;
				else l=mid+1;
			}
			aa=l;
		}
		else aa=tot+1;
		if (b[tot]<x)
		{
			for (l=1,r=tot;l!=r;)
			{
				mid=l+r>>1;
				if (b[mid]<x) r=mid;
				else l=mid+1;
			}
			bb=l;
		}
		else bb=tot+1;
		
		if (aa==bb)
		{
			cnt[++tot]=1;
			a[tot]=b[tot]=x;
		}
		else if (aa<bb) a[aa]=x,cnt[aa]++;
		else b[bb]=x,cnt[bb]++;
	}
	int maxx=1;
	for (int i=1;i<=tot;++i) maxx=max(maxx,cnt[i]);
	printf("%d %d",tot,maxx);
	return 0;
}

H 简单的问题

思路

全场最难的题,思路比较长且难想但是没什么超纲知识点于是还是放上来了 qwq

引理一

A,BA,B 是任意两个不同的有限集合,则 2AB×ABA2+B212|A\cap B|\times|A\cup B|\le |A|^2+|B|^2-1。当且仅当 A 和 B 存在包含关系,且两者元素个数相差 11 时等号成立。下面给出证明:

AB=xA(AB)=yB(AB)=z|A\cap B|=x\\ |A\setminus(A\cap B)|=y\\ |B\setminus(A\cap B)|=z

x,y,zNy+z1A=x+yB=x+zAB=x+y+zx,y,z\in N\\ y+z \ge 1\\ |A|=x+y\\ |B|=x+z\\ |A\cup B=x+y+z|

带入原式可得

A2+B22AB×AB=(x+y)2+(x+z)22x(x+y+z)=y2+z21\begin{aligned} &|A|^2+|B|^2-2|A\cap B|\times|A\cup B|\\ =&(x+y)^2+(x+z)^2-2x(x+y+z)\\ =&y^2+z^2\ge 1 \end{aligned}

当且仅当 yyzz 分别为 0011 时,等号成立。

引理二

1,2,,n{1,2,\dots,n} 的全部子集为 A1,A2,,A2nA_1,A_2,\dots,A_{2^n},则一定存在 A{A} 的一个排列 B1,B2,,B2n{B_1,B_2,\dots,B_{2^n}} 使得 Bi(1in)B_i(1\le i\le n)Bimod2n+1B_{i \mod 2^n +1} 存在包含关系,且元素数量之差为 11。下面给出证明:

n=2n=2 时,集合 1,2{1,2} 的四个子集排列成 ,{1},{1,2},{2}\emptyset,\{1\},\{1,2\},\{2\} 即可满足条件。

假设命题对 nn 成立,且 B1,B2,,B2nB_1,B_2,\dots,B_{2^n} 是一个满足条件的排列,则对于 n+1n+1,取以下排列方式即可满足条件:

B1,B2,...,B2n,B2n{n+1},B2n1{n+1},,B1{n+1}B_1,B_2,...,B_{2^n},B_{2^n}\cup \{n+1\},B_{2^n-1}\cup \{n+1\},\dots,B_1\cup \{n+1\}

由数学归纳法,命题得证。

引理三

k=0nk2Cnk=n(n+1)2n2\sum_{k=0}^n k^2 C_n^k=n(n+1)2^{n-2}

证明如下:

k=0nk2×Cnk=k=0nk×n×Cn1k1=n[k=0n(k1+1)×Cn1k1]=n[k=0n(k1)×Cn1k1+k=0nCn1k1]=n[k=0n(n1)×Cn2k2+2n1]=n[(n1)×2n2+2n1]=n×(n+1)×2n2\begin{aligned} &\sum_{k=0}^n k^2\times C_n^k\\ =&\sum_{k=0}^n k\times n\times C_{n-1}^{k-1}\\ =&n[\sum_{k=0}^n(k-1+1)\times C_{n-1}^{k-1} ]\\ =&n[\sum_{k=0}^n(k-1)\times C_{n-1}^{k-1} + \sum_{k=0}^nC_{n-1}^{k-1}]\\ =&n[\sum_{k=0}^n(n-1)\times C_{n-2}^{k-2} + 2^{n-1}]\\ =&n[(n-1)\times 2^{n-2}+2^{n-1}]\\ =&n\times(n+1)\times 2^{n-2} \end{aligned}

综上所述:

由引理一可知

i=12nAiAimod2n+1×AiAimod2n+1i=12nAi2+Aimod2n+1212=i=12nAi22n1=k=0nCnk×k22n1\begin{aligned} &\sum_{i=1}^{2^n} |A_i\cap A_{i\mod 2^n+1}|\times|A_i\cup A_{i\mod 2^n+1}|\\ \le&\sum_{i=1}^{2^n} \frac{|A_i|^2+|A_{i\mod 2^n+1}|^2-1}{2}\\ =&\sum_{i=1}^{2^n} |A_i|^2-2^{n-1}\\ =&\sum_{k=0}^n C_n^k\times k^2-2^{n-1} \end{aligned}

使用引理二中的排列方法可以使得等号成立,又由引理三可知,HnH_n 的最大值为

k=0nCnk×k22n1=n(n+1)2n22n1\sum_{k=0}^n C_n^k\times k^2-2^{n-1}=n(n+1)2^{n-2}-2^{n-1}

所以本题的答案为对每个询问的 nn,输出 (n2+n2)×2n2(n^2+n-2)\times2^{n-2} 即可。

代码

#include <stdio.h>
using namespace std;
using LL=long long;
const LL mod=1e9+7;
LL poww(LL a,LL b)
{
	a%=mod;
	LL ans=1;
	for (;b;b>>=1,a=a*a%mod)
		if (b&1) ans=ans*a%mod;
	return ans;
}
int main()
{
	int T;
	LL n,t;
	for (scanf("%d",&T);T--;)
	{
		scanf("%lld",&n);
		if (n==1)
		{
			printf("0\n");
			continue;
		}
		t=poww(2,n-2);
		n%=mod;
		printf("%d\n",(n*n%mod+n+mod-2)%mod*t%mod);
	}
	return 0;
}

I 种瓜得瓜,种豆得豆

思路

i(1in)i(1\le i\le n) 棵气球当前的美味度是 aia_i,我们令 bi=kaib_i=k-a_i 表示第 ii 棵气球的最佳收获期限,同时也是最后收获期限。

k1k-100 枚举当前时刻 tt,此时我们可以选择收获所有满足 bitb_i\ge t 的没有在未来被收获的气球。假设我们选择此刻收获气球 jj,那么对答案的贡献显然为 kbj+tk-b_j+t,为了最大化答案,应该选择收获 bb 值最小的气球。

bb 数组进行过排序,枚举当前时刻 tt 的同时双指针将符合条件bi>tb_i>t 的气球压进栈里,每个时刻收获栈顶的气球即可。

代码

#include <stdio.h>
#include <algorithm>
#include <vector>
#include <math.h>
using namespace std;
const int N=200005;
using LL=long long;
LL ans;
int a[N],b[N],stk[N],n,p,k;
int main()
{
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;++i) scanf("%d",&a[i]),b[i]=k-a[i];
	sort(b+1,b+1+n);
	for (int t=k-1,j=n;t>=0;--t)
	{
		while (j>=1&&b[j]>=t) stk[++p]=b[j--];
		if (p) ans+=k-stk[p--]+t;
	}
	printf("%lld\n",ans);
	return 0;
}

J 数组划分

思路

kk 进行分类讨论:

  • k=1k = 1 则无法将任何元素分离成一个单独子段,故取所有元素最小值。
  • k=2k = 2 则可以将第一个元素或者最后一个元素分离成单独的一个子段,故取第一个和最后一个的较大值。
  • k3k \ge 3 则可以分离出任何一个元素作为单独的子段,故取所有元素最大值。

发现数据水了被一堆人直接输出数组最大值碾过(

代码

#include <stdio.h>
int a[200005];
int main()
{
	int n,k,minn=1e9+1,maxx=0;
	scanf("%d%d",&n,&k);
	for (int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);
		if (a[i]>maxx) maxx=a[i];
		if (a[i]<minn) minn=a[i];
	}
	if (k>=3) printf("%d",maxx);
	else if (k==1) printf("%d",minn);
	else if (a[1]>a[n]) printf("%d",a[1]);
	else printf("%d",a[n]);
    return 0;
}

总结

榜单看上去不太丑,前排很有区分度 qwq

但是中档题略少,看起还还是让相当一部分同学在赛中感到无聊了对不起 orz

不太想出原题或者板子题,所以看起来可能会有点像是脑筋急转弯 orz

发现有部分同学完全不理解多测(在一个测试点中输入 TT 组测试数据),导致本来可以做出的题写不出来,非战之罪,也很对不起(

本来以为昨天赛中没有什么锅的但是今天又查看代码才发现 J 数据弱了orz