"蔚来杯"2022牛客暑期多校训练营1


  A B C D E F G H I J K
赛时过题 O     O     O        
赛后补题                      

赛后感悟:

1、无论当前榜单如何,最好所有题目都要看,以免歪榜导致可做题没做!(C题歪榜导致没人做)

2、开题时最好要模拟一下样例,把样例搞清楚的同时可能发现一些优秀的性质!(J题的性质通过模拟样例是可以猜到的)

3、不要陷入思维死胡同,一旦自己的思路长时间想不通一定要多和队友交流!(I题自己的做法碰壁后没和队友充分交流,而是钻死胡同了)

赛时排名情况:

5题末尾:56名

4题末尾:304名


G Lexicographical Maximum

题目难度:Check-in(签到)

题目大意:给定 n,求出 1, 2, . . . , n 对应的数字字符串 中字典序最大的那个字符串

数据范围:1 ≤ n ≤ 101000000

题目解析:

显然,为了让字典序最大,能放9就放9,直到最后一位

如果n除了最后一位都是9,那么最后一位也填上

否则为了保证字符串<=n,那么就不能填最后一位(如n=621时输出99而非990)

赛后总结:很快就发现了签到题并一发AC,干得不错!

参考代码:

查看代码
#include
#include
#include
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e6+1000;
char s[N];int len;
int check()
{
	For(i,1,len-1) if (s[i]!='9') return 0;
	return 1;
}
int main()
{
	scanf("%s",s+1);len=strlen(s+1);
	For(i,1,len-1) putchar('9');
	if (check()) putchar(s[len]);
	return 0;
} 

A Villages: Landlines

题目难度:Easy

题目大意:

数轴上有恰好一个发电站与 n ? 1 个建筑物

在数轴上放置一些电力塔使得所有建筑物通过电力塔与发电站连通

能源站位于 xs,能与距离 rs 内的电力塔直接连通

第 i 个建筑物位于 xi,能与距离 ri 内的电力塔直接连通

可以消耗 |xA ? xB| 长度的电线连通位于 xA 与 xB 的电力塔

最小化消耗的电线长度。

数据范围:1≤n≤2×105?109xs?1091rs?109

题目解析:阅读理解题。题面很长,做法很简单。

等价于有 n 个区间 [xs ? rs, xs + rs], [xi ? ri, xi + ri]

先合并所有有交集的区间,形成大区间(可以在子区间中交集处放任意多电力塔,让这些子区间通过电力塔形成大区间)

最后填补大区间之间的空隙即可(即在大区间的边界处放电力塔,相邻大区间边界的电力塔再连电线)

赛后总结:丁老师和宇彬很快就做出来了,一发AC,good job!

参考代码:

查看代码
 #include
#include
#include
#define For(i,a,b) for(int i=a;i<=b;i++) 
const int N=2e5+1000;
int n;std::pair p[N];
int main()
{
	scanf("%d",&n);
	For(i,1,n)
	{
		int x,r;scanf("%d%d",&x,&r);
		p[i]=std::make_pair(x-r,x+r);
	}std::sort(p+1,p+1+n);
	int max=p[1].first;
	long long ans=0;
	For(i,1,n)
	{
		if (p[i].first>max) ans+=p[i].first-max,max=p[i].second;
		else max=std::max(max,p[i].second);
	} 
	printf("%lld\n",ans);
	return 0;
}

D Mocha and Railgun

题目难度:Easy

题目大意:

T组数据

给定一个圆心在原点、半径为r的圆和严格位于圆内的一点 P(x,y)

Mocha 会从点 P 向任意角度发射一个长度为 2d 的电磁炮

电磁炮底边的中点为点 P 且保证无论怎么转两端一直位于圆内

询问单次发射能摧毁的最大圆弧长

数据范围:

1 ≤ T ≤ 1000, ?109 ≤ x, y ≤ 109, 1 ≤ r, d ≤ 109

标准解析:

无论当前的电磁炮旋转角度如何,我们可以固定电磁炮的方向,将点 Q 绕原点旋转,从而使得电磁炮方向竖直向上(即 y 轴正方向)。

那么我们可以将题目转化成为电磁炮的方向总是竖直向上的,点 Q 绕原点旋转一周的过程中可以摧毁的最长墙壁长度。

并且题目保证了 base segment 总是位于圆内部的,那么我们设点 Q 到原点距离是 dis,点 Q 绕原点旋转一周就可以转化成点 Q 在以 (?dis, 0) 和 (dis, 0) 为端点的线段上移动。

显然摧毁的墙壁是一段劣弧,那么长度的大小关系就可以映射到弦长的大小关系。

设电磁炮摧毁墙壁的端点为 PL PR,则对应弦长长度为√((yPL ? yPR )2 + (2d)2),其中 d 是给定的,那么显然两点纵坐标之差的绝对值最大时弦长最长。

对交点坐标求导不难看出当Q位于线段端点时差的绝对值最大,返回原题面,也就是 base segment 位于直线 QO 上时摧毁墙壁最长。(其实通过直观感知也能看出来)

赛时经历:(题目难点:卡精度)

(1)首先无论P在哪,根据圆的对称性都可以转到原点O的正上方

然后直观上认为电子炮向正上/下方发射,摧毁的圆弧最短

那么就是说在电磁炮在转的时候摧毁的圆弧长会先变长再变短

采用三分,由于精度损失WA了一发

(2)后来发现大部分数据在三分的时候都会电子炮以90°为最优(即向正左/右发射)

为了必变精度损失导致三分后的最终l,r错误,再交一发只取90°的做法,由于精度损失又WA了

ldouble c=acos(dot(px1,py1,px2,py2)/(dis(px1,py1)*dis(px2,py2)))

(3)发现在算圆心角的时候如果采用点积直接计算,计算时涉及开根号、除法、反三角,误差较大

不如直接计算该线段相对于某个线段的圆心角,再一减就出来了。计算只涉及除法、反三角,减少误差。AC!

ldouble c=ABS(asin(py2/r)-asin(py1/r));

参考代码:

查看代码
 #include
#include
#include
#define ldouble long double
#define ABS(x) ((x)<0?-(x):(x))
const ldouble pi=acos(-1.0);
ldouble dis(ldouble x,ldouble y)
{
	return sqrt(x*x+y*y);
}
ldouble getlen(ldouble x1,ldouble y1,ldouble b,ldouble r)
{
	ldouble A=1;
	ldouble B=2*x1*cos(b)+2*y1*sin(b);
	ldouble C=x1*x1+y1*y1-r*r;
	return (-B+sqrt(B*B-4*A*C))/(2*A);
}
ldouble dot(ldouble x1,ldouble y1,ldouble x2,ldouble y2)
{
	return x1*x2+y1*y2;
}
ldouble calc(ldouble xq,ldouble yq,ldouble d,ldouble a,ldouble r)
{
	ldouble x1=xq+d*cos(a);
	ldouble y1=yq+d*sin(a);
	ldouble x2=xq-d*cos(a);
	ldouble y2=yq-d*sin(a);
	ldouble b=a+pi/2;
	ldouble len1=getlen(x1,y1,b,r);
	ldouble len2=getlen(x2,y2,b,r);
	ldouble px1=x1+len1*cos(b);
	ldouble py1=y1+len1*sin(b);
	ldouble px2=x2+len2*cos(b);
	ldouble py2=y2+len2*sin(b);
//	ldouble c=acos(dot(px1,py1,px2,py2)/(dis(px1,py1)*dis(px2,py2)));
	ldouble c=ABS(asin(py2/r)-asin(py1/r));
	printf("%.6Lf %.6Lf\n",a/pi*180,c*r);
	return c*r;
}
ldouble calc2(ldouble xq,ldouble yq,ldouble d,ldouble a,ldouble r)
{
	ldouble x1=xq;
	ldouble y1=yq+d;
	ldouble x2=xq;
	ldouble y2=yq-d;
	ldouble py1=y1;
	ldouble px1=sqrt(r*r-py1*py1);
	ldouble py2=y2;
	ldouble px2=sqrt(r*r-py2*py2);
//	ldouble b=a+pi/2;
//	ldouble len1=getlen(x1,y1,b,r);
//	ldouble len2=getlen(x2,y2,b,r);
//	ldouble px1=x1+len1*cos(b);
//	ldouble py1=y1+len1*sin(b);
//	ldouble px2=x2+len2*cos(b);
//	ldouble py2=y2+len2*sin(b);
  	ldouble c=ABS(asin(py2/r)-asin(py1/r));
//	ldouble c=acos(dot(px1,py1,px2,py2)/(dis(px1,py1)*dis(px2,py2)));
	return c*r;
}
int main()
{
	int T;scanf("%d",&T);
	while (T--)
	{
		ldouble sr,xq,yq,d;scanf("%Lf%Lf%Lf%Lf",&sr,&xq,&yq,&d);
		yq=dis(xq,yq);xq=0;
//		ldouble l=0,r=pi;
//		while (r-l>1e-10)
//		{
//			ldouble lmid=l+(r-l)/3;
//			ldouble rmid=r-(r-l)/3;
//			if (calc(xq,yq,d,lmid,sr)

I Chiitoitsu

题目难度:mediun-easy

题目大意:

一共有34种麻将牌,每种麻将牌一共有4张。

初始手牌有 13 张麻将牌,其中相同种类牌至多出现 2 张

每轮可以从牌堆摸牌:

①若达成七对子则自摸胡牌

②若不然则选择手牌中某张牌并丢弃之

给定初始手牌,求最优策略下达成七对子的期望轮数

多组数据,数据组数不超过 105

题目解析:题面较长,但做法不难。

设抽到了某一个手牌:

①如果这个手牌能和手里的某个单牌配对,就丢掉当前手牌中的某另外一个未配对单牌

②如果这个手牌不能和手里某个单牌配对,那就把这个新收到的单牌丢了

(因为一直保持老单牌,对于老单牌来说牌堆里一直有3个牌能和它配对)

(如果丢了老单牌,牌堆里最多有3个牌能和新单牌配对,如果一直随便乱丢牌堆里甚至都不到3个能和它配对)

通过DP求期望,令dp[i][j]表示当前牌堆里有i张牌,自己还有j个单牌(如果采用上述策略,牌堆里剩余的i张牌一定有j*3张的种类和当前手牌配对,其他都是要丢的,故无需记录)

需要注意的是如果新抽到的牌和当前手牌配对了,之后再抽到该种类牌就要丢弃了,即配对后把该种类牌当做i-j*3的那部分处理

dp[i][j]=1+(j*3/i)*dp[i-1][j-2]+((i-j*3)/i)*dp[i-1][j] (i>j*3)
dp[i][j]=1+dp[i-1][j-2] (i==j*3)

由于牌数为奇数,某种类牌<=2,故单牌数一定也为奇数,故边界情况一定是只剩一张单牌,最后再抽一张牌和它配对(不可能开局即胜利)

dp[i][1]=1+(j*3/i)*0+((i-j*3)/i)*dp[i-1][1] (i>3)
dp[i][1]=1(i==3)

赛后总结:

赛时一直往期望可加性去钻牛角尖。。。不要陷入思维死胡同,一旦自己的思路长时间想不通一定要多和队友交流!

1、为什么这个期望DP是对的呢?

每一个dp[i][j]其实相当于一个随机变量Xi,j

dp[i][j]=1+(j*3/i)*dp[i-1][j-2]+((i-j*3)/i)*dp[i-1][j]    实际上是数学期望的定义式,只不过dp[i-1][j-2]和dp[i-1][j]是待求项

2、同种类的牌会不会发生重复等问题?拿牌的顺序对答案是否有影响?

同种类的牌不会发生重复问题。如果当前该种类的牌可与当前牌配对,那就是(j*3/i)*dp[i-1][j-2];否则(已经配对成功/手牌中无该类别单牌)就是(i-j*3)/i)*dp[i-1][j-2]。

拿牌的顺序已经在DP过程中考虑了,实际上DP就是对拿牌过程的模拟。

比如当前有手牌1m:

先抽到1m再抽到1n:dp[i][j]->dp[i-1][j-2]->dp[i-2][j-2]

先抽到1n再抽到1m:dp[i][j]->dp[i-1][j]->dp[i-2][j-2]

3、到底什么是期望可加性?一些概率论的基本概念?

随机变量是个映射,将样本空间中的样本点映射到实数上。X=X(w)表示样本点w被映射为实数X上了。比如{X<=x}={w|X(w)<=x}就是是一个包含若干样本点的事件。

例题:给出一棵含n个白点的有根树,每次随机选择一个还没有被染黑的节点,将这个节点和这个节点子树中的所有点染黑。问期望操作多少次后所有点都被染黑?

做法:设总操作次数为随机变量X,某个点的操作次数为随机变量Yi,则X=sum(Yi),则E(X)=E(sum(Yi))=sum(E(Yi))。(第二个等号为期望可加性)

又Yi=1,当前点到根节点这depth[i]个点中,当前点被第一个染黑(p=1/depth[i])

      =0,当前点到根节点这depth[i]个点中,当前点不是第一个被染黑(p=1-1/depth[i])

故E(Yi)=depth[i]

注意到期望可加性满足的条件是随机变量X可表示为若干子随机变量Y之和,即子随机变量X和Y经历的操作过程是完全一致的,只是计算的对象不一样。

这道题的阉割版:设桶里有n个白球和m个黑球,每次随机从桶里抽出一个球,直到抽出某个黑球为止。要求取球次数的期望。

设某个白球的取球次数为X,则

X=1,该球在第一个黑球之前被取到,p=

     0,该球在第一个黑球之后被取到

参考代码:

查看代码
 #include
#include
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=34*13+500;
const long long mod=1e9+7;
long long dp[N][N];
char s[13*2+100];
long long power(long long x,long long y)
{
	long long ans=1;
	while (y)
	{
		if (y&1) ans=ans*x%mod;
		y>>=1;x=x*x%mod;
	}
	return ans;
}
int main()
{
	For(j,1,1) For(i,3*j,34*4-13) 
	{
		if (i>3*j) dp[i][j]=(1+dp[i-1][j]*(i-j*3)%mod*power(i,mod-2)%mod)%mod;
		else dp[i][j]=1;//dp[3][1]=1,抽一张牌后必胜
	}
	for(int j=3;j<=13;j++) For(i,j*3,34*4-13)//单牌一定是奇数,且最多13张即一开始全是单牌
	{
		if (i>3*j) dp[i][j]=(1+dp[i-1][j]*(i-j*3)%mod*power(i,mod-2)%mod+dp[i-1][j-2]*(j*3)%mod*power(i,mod-2)%mod)%mod;
		else dp[i][j]=(1+dp[i-1][j-2])%mod;//必中
	}
	int TAT;scanf("%d",&TAT);
	For(T,1,TAT)
	{
		scanf("%s",s+1);
		int temp=0;
		For(i,1,13) 
		{
			int flag=0;
			For(j,1,i-1) if (s[(j-1)*2+1]==s[(i-1)*2+1]&&s[j*2]==s[i*2]) 
			{
				flag=1;break;
			}
			if (!flag) temp++;
			else temp--;
		}
		printf("Case #%d: %lld\n",T,dp[34*4-13][temp]);
	}
	return 0;
}

相关