HIT2021五一集训(一)

2020第十五届黑龙江省赛的题目,怪不得眼熟
(状态很不好

A. August

Gym 102803A

题意:微积分结论题。(ans=pi a^2+4ab)。其中,可以使用(pi=acos(-1))来求(pi)
具体过程:显然图形在x轴上方的部分的面积相当于以a为半径的圆形面积。
现在分析图形在第三象限部分的面积。

(y=frac{2b}{pi}(arccos(frac{x+a}{a})-pi))

所以,(x=a*cos(frac{2b}{pi}y+pi)-a)

(S=-egin{matrix} int_{-2b}^{0} (a*cos(frac{2b}{pi}y+pi)-a)\, dy end{matrix})

(S=-aegin{matrix} int_{-2b}^{0} cos(frac{2b}{pi}y+pi)\, dy+2ab end{matrix})

(令z=frac{2b}{pi}y,S=-frac{api}{2b}egin{matrix} int_{-pi}^{0} cos(z+pi)\, dz+2ab end{matrix})

(S=-frac{api}{2b}*(sin(pi)-sin(0))+2ab=2ab)

所以图形在x轴下方的面积为(2*2ab=4ab)
加上上半部分即(pi a^2+4ab)
(应该没有推错的地方吧,一百年没做微积分了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double PI=acos(-1);
int main()
{
	int T;
	double a,b,ans;
	scanf("%d",&T);
	while (T--)
	{
		scanf("%lf%lf",&a,&b);
		ans=PI*a*a+4.0*a*b;
		printf("%.8lf
",ans);
	} 
	return 0;
}

C. Cornelia Street

Gym 102803C

题意:给你一个串S,已知S=AA...ABB...BAA...Aa,其中|A|=|B|,A!=B,a为A的真前缀,即满足0<=|a|<|A|,问最短的A,B

思路:看上去很难。(可能是因为我脑子不大好使)。我甚至用了kmp(刚开始想歪的结果,实际上好像并不需要)

枚举A的长度,然后不断检查下一段字符串是否与A相同;若不同,则认为读到了字符串B,然后不断检查下一段字符串是否与B相同;若不同,则比对这段字符串与A,若还是不同,则退出进行下一次枚举;否则继续不断检查下一段字符串是否与A相同。

(所以就是个枚举题,我为啥会想到一些七七八八的东西呢。。。

代码懒得改了,并不需要用kmp(

说起来这个|S|<8e5,居然能过。。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e6+5;
char s[maxn];
int p[maxn];
void prework(char b[],int len)
{
	p[0]=-1;
	int i,j=-1;
	for (i=1;i<len;i++)
	{
		while (j>=0 && b[j+1]!=b[i]) j=p[j];
		if (b[j+1]==b[i]) j++;
		p[i]=j;
	}
}
int main()
{
	int i,j,k,flag,be,n,T,ans,len,Aa;
	scanf("%s",s);
	len=strlen(s);
	prework(s,len);
	//for (i=0;i<len;i++) printf("%d ",p[i]);
	//printf("
");
	
	Aa=p[len-1];
	//printf("len:%d %d
",len,Aa);
	for (i=2;i<=Aa+1;i++)//枚举A长度 
	{
		flag=0;
		for (j=i;j<len;j+=i)
		{
			//printf("ij %d %d %d %c
",i,j,flag,s[j]);
			if (flag==0)
			{
				for (k=0;k<i;k++)
				{
					if (j+k>=len ||s[k]!=s[j+k])
					{
						flag=1;break;//读到B串了 
					} 
				}
				if (flag==1)
				{
					//if (k==0) 
					be=j;
					//else break;//失败 
				}
			}
			else if (flag==1)
			{
				for (k=0;k<i;k++)
				{
					if (j+k>=len || s[be+k]!=s[j+k])
					{
						flag=2;break;//读到后面的A串了 
					}
				}
				if (flag==2)
				{
					for (k=0;k<i;k++)
					{
						if (j+k>=len ||s[k]!=s[j+k])
						{
							flag=0;break;//这不是A串 
						} 
					}
					if (flag==0) break; 
				}
			}
			else if (flag==2)
			{
				for (k=0;k<i;k++)
				{
					if (j+k>=len) break;
					if (s[k]!=s[j+k])
					{
						flag=0;break;
					}
				}
				if (flag==0) break;//失败
			}
			//printf("ij %d %d %d %c
",i,j,flag,s[i]);
		}
		if (flag==2)
		{
			for (j=0;j<i;j++) printf("%c",s[j]);
			printf(" ");
			for (j=0;j<i;j++) printf("%c",s[be+j]);
			printf("
");
			break;
		}
	}
	return 0;
}

F. False God

Gym 102803F

题意:给一个棋盘,棋盘里有一个Kin棋和n个Fu棋。
若当前Kin棋在(x,y),那么它可以移动一步,到(x+1,y),(x+1,y+1),(x,y+1),(x−1,y+1),(x−1,y),(x,y−1);若当前Fu棋在(x,y),那么它可以移动一步,到(x,y-1)。
现在Kin棋先手,当Kin棋移动一步后,每一个Fu棋都会移动一步。
若任意棋移动后,Kin与Fu重合,那么算做Kin吃掉了Fu。
现在告诉你棋盘的情况,问Kin最多能吃掉多少个Fu。
思路:Fu棋的运动是固定的,显然可以发现,除了Fu在Kin的下方一格的情况,所有y坐标小于Kin的y坐标的Fu,Kin都不能吃到。所以我们可以删掉这些Fu不做考虑。
假设当前Kin在(e[i].x,e[i].y),Fu在(e[j].x,e[j].y),那么,如果|e[i].x-e[j].x|<=|e[i].y-e[j].y|+1,那么Kin就能够吃到这个Fu。
(于是开始时我就按照这个公式,在满足条件的两个Fu(或者一个Fu和一个Kin)之间连边,bfs跑最长路,然后发现,这样会出现重复经过一个点的情况。。。需要记录当前每个点的访问情况,然而n<1e3,看上去是无法用bfs解决的。。。然后又想到用dfs,然后很显然地TLE了。
(参考了一下别人的想法之后)对于三个点1,2,3,若它们之间都有边,且y1<y2<y3,从1开始计算,显然先考虑2是比较合理的,因为Kin相对于Fu无法下移(即它们的(Delta y)无法变小)。
于是将所有的Fu按照y坐标从小到大排序。记f[]为Kin(0)到达Fu(i)时吃掉的Fu的数量,初始化f[0]为0表示Kin在初始位置,其余的f均设为负无穷。
对于一个Fu(i),用y坐标小于它且满足前文的公式的Kin或Fu(j)更新答案,即f[i]=max(f[i],f[j]+1)。
然后f[]的最大值(还要加上处于初始Kin下方一格的Fu)就是答案。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int maxn=1005;
const int inf=2e9;
int f[maxn];
struct node
{
	int x,y;
}e[maxn];
bool cmp(node a,node b)
{
	if (a.y==b.y) return a.x<b.x;
	return a.y<b.y;
} 
int main()
{
	int i,j,T,n,m,x,y,dx,dy,add,ans;
	scanf("%d",&T);
	while (T--)
	{
		m=0;ans=0;add=0;
		scanf("%d%d%d",&e[0].x,&e[0].y,&n);
		for (i=1;i<=n;i++) 
		{
			scanf("%d%d",&x,&y);
			if (e[0].x==x && e[0].y-1==y) add++;
			else if (y>=e[0].y)
			{
				e[++m].x=x;
				e[m].y=y;
				f[m]=-inf;
			}
		}
		sort(e+1,e+m+1,cmp);
		f[0]=0;
		for (i=1;i<=m;i++)
		{
			for (j=0;j<i;j++)
			{
				dx=e[i].x-e[j].x;
				if (dx<0) dx=-dx;
				dy=e[i].y-e[j].y;
				if (dx<=dy+1)
				{
					f[i]=max(f[i],f[j]+1);
				}
			}
			ans=max(ans,f[i]);
		}
		ans+=add;
		printf("%d
",ans);
	}
	return 0; 
}

G. Goodbye

Gym 102803G

题意:一种游戏,两名玩家轮流取数。第一轮的第一个取数的玩家可以取一个n的真因子d,(真因子就是不为1且不为本身的因子),接下来下一个玩家将取一个上一个玩家取的数的真因子,比如对于第一轮第二个取数的玩家,就要取d的一个真因子。如此轮流进行,直至有一位玩家无法再取出一个真因子时,该玩家获胜。问在先手获胜的条件下,先手在第一轮能够取的最大数是什么。

思路:乍一看像博弈论,然而并不是。仔细分析只有三种情况。

1、存在a,b满足n%(ab)==0,并且ab!=n,其中a,b均为n的真质数因子。(或者换一种表述,n可以分解为两个以上质数的乘积。)此时先手取走a*b,那么后手只能取a或b,先手必胜。

2、n等于两个质数的乘积,即n=a*b。此时先手只能取a或b,后手必胜。

3、n为质数,先手直接获胜。

第2种情况直接输出-1,第3种情况直接输出0。而对于第1种情况,只需在n的所有真质数因子中找到最大的两个,它们的乘积就是先手能取的最大数。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn=1e5+5;
int ip[maxn],pr[maxn],cnt;
void pre()
{
	int i,j;
	for (i=2;i<=1e5;i++)
	{
		if (!ip[i]) pr[++cnt]=i;
		for (j=1;j<=cnt && pr[j]*i<=1e5;j++)
		{
			ip[pr[j]*i]=1;
			if (i%pr[j]==0) break;
		}
	}
}
int work(int n)
{
	int i,re=1,now=0;
	for (i=cnt;i>=1;i--)
	{
	    if (pr[i]>n) continue; 
		while (n%pr[i]==0) 
		{
			//printf("n %d %d
",n,pr[i]);
			if (!now) re=pr[i],now=1;
			else 
			{
				return re*pr[i];
			}
			n=n/pr[i];
		}
	} 
	return 0;
}
int main()
{
	int i,n,T,ans,nans;
	pre();
	//for (i=1;i<=cnt;i++) printf("%d
",pr[i]);
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		ans=work(n);
		//printf("ans:%d
",ans);
		if (ans==0)
		{
			printf("0
");
		}
		else 
		{
			if (ans!=n) printf("%d
",ans);
			else printf("-1
");
		}
	} 
	return 0;
}

H. Hate That You Know Me

Gym 102803H

题意:定义 (sigma_k(n)=sum_{d|n}^{}{d^k})
给出a,b,n,求(left(left(sum_{i=1}^n{sigma_a(i)} ight) oplus left(sum_{i=1}^n{sigma_b(i)} ight) ight)quad mod quad 2^{64})
其中 (oplus)表示按位异或。

思路:整除分块题。就是这个取模有点难搞。。。搞半天不过只好参考别人的写法了。

关于数论分块问题,大概就是,求解类似于(sum_{i=1}^{n}{lfloor frac{n}{i} floor})的式子时,由于(lfloor frac{n}{i} floor)单调不增,所以存在多个区间([l,r]),使得对于(forall i,jin[l,r]),都有(lfloor frac{n}{i} floor)=(lfloor frac{n}{j} floor)成立。
对于任意一个(i),满足上式的最大的(j)可由(j=igg lfloor frac{n}{ ig lfloor frac{n}{i} ig floor} igg floor)求出。
根据以上的结论,我们可以把([1,n])分成个区间(块)。
对于([l,r])区间,它对答案的贡献为({lfloor frac{n}{l} floor}*(r-l+1))

然后回到这道题,分析后可以发现(sum_{i=1}^n{sigma_k(i)}=sum_{i=1}^n{i^k lfloor frac{n}{i} floor})
而题目中所给的a,b有范围(0le a,b<4),即只有0,1,2,3四种取值。

1至n的0次方之和即为n,

1次方和即为(frac{n(n+1)}{2})

平方和有公式(frac{n(n+1)(2n+1)}{6})

立方和有公式([frac{n(n+1)}{2}]^2)

于是对于每一段([l,r]),根据(l至r的k次方和)*(r-l+1)统计答案,就可求出(sum_{i=1}^n{sigma_k(i)})

由于mod (2^{64}),使用unsigned long long。

(关于数论分块,顺便贴一个网上找的笔记

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef unsigned long long ull;
ull npow(ull n)
{
	ull a=n,b=n+1;
	if (a&1) b>>=1;
	else a>>=1;
	ull re=a*b;
	return re;
}
ull nsquare(ull n)
{
	ull a=n,b=n+1,c=2*n+1;
	if (a&1) b>>=1;
	else a>>=1; 
	if (a%3==0) a/=3;
	else if (b%3==0) b/=3;
	else c/=3;
	ull re=a*b*c;
	return re;
}
ull ncube(ull n)
{
	ull a=n,b=n+1;
	if (a&1) b>>=1;
	else a>>=1; 
	ull re=a*b;
	re=re*re;
	return re;
}
ull block(ull op,ull n)
{
	ull l,r,re,ans=0;
	for (l=1;l<=n;l=r+1)
	{
		r=n/(n/l);
		if (r>n) r=n;
		re=(ull)(n/l);
		if (op==0) re=re*(r-l+1);
		else if (op==1) re=re*(npow(r)-npow(l-1));
		else if (op==2) re=re*(nsquare(r)-nsquare(l-1));
		else if (op==3) re=re*(ncube(r)-ncube(l-1));
		ans+=re; 
		//printf("re %lld %lld %d %d
",re,ans,l,r);
	}
	return ans;
}
int main()
{
	ull a,b,n,ans,ansa,ansb;
	scanf("%llu%llu%llu",&a,&b,&n);
	ansa=block(a,n);
	ansb=block(b,n);
	ans=ansa^ansb;
	//printf("%lld %lld
",ansa,ansb);
	printf("%llu
",ans);
	return 0;
}

L. Let's Get Married

Gym 102803L

题意:...懒得概括了。总之是细节题,不难但是容易出错。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
	int T,op;
	long long i,id,nx=0,ny=0,x,y;
	long long rou,top,ans,rr,ll,dd;
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&op);
		if (op==1)
		{
			scanf("%lld",&id);
			rou=(id-1)/2;
			rou=sqrt(rou);
			for (i=max(rou-1,(long long)0);;i++)
			{
				if (1ll*2*i*(i-1)+1>id) break;
			}
			rou=i-1;
			top=2*rou*(rou-1)+1;//最上 
			rr=top+2*rou-1;//最右 
			dd=top+3*rou-1;//最下
			ll=top+4*rou-1;//最左 
			//printf("rou %lld %lld %lld %lld %lld
",rou,top,rr,dd,ll);
			if (id==0) 
			{
				x=0;y=0;
			}
			else if (id<=rr)//上面 
			{
				y=rou-(id-top+1)/2;
				if (id%2==0) x=rou-y;
				else x=y-rou;
			} 
			else//下面 
			{
				if (id<dd)
				{
					y=rr-id;
					x=rou+y;
				}
				else 
				{
					y=id-ll;
					x=-(rou+y);
				} 
			} 
			//printf("ans1: %lld %lld
ans1: ",x,y);
			printf("%lld %lld
",x-nx,y-ny);
			nx=x;ny=y;
		}
		else 
		{
			scanf("%lld%lld",&x,&y);
			if (x>=0 && y>=0)
			{
				rou=x+y;
				top=2*rou*(rou-1)+1;//最上
				ans=top-1; 
				for (i=1;i<=x;i++) ans+=2;
			}
			else if (x>=0 && y<0)
			{
				rou=x-y;
				top=2*rou*(rou-1)+1;
				ans=top+2*rou-1;//最右 
				for (i=rou-1;i>=x;i--) ans++;	
			}
			else if (x<0 && y<=0)
			{
				rou=-x-y;
				top=2*rou*(rou-1)+1;
				ans=top+3*rou-1;//最下 
				for (i=-1;i>=x;i--) ans++;
			} 
			else if (x<0 && y>0)
			{
				rou=-x+y;
				top=2*rou*(rou-1)+1;//最上
				ans=top;
				for (i=-1;i>=x;i--) ans+=2;
			}
			//printf("ans2: ");
			printf("%lld
",ans);
			nx=x;ny=y;
		}
	} 
	return 0;
}
原文地址:https://www.cnblogs.com/lsykk/p/14767379.html