2020第十五届黑龙江省赛的题目,怪不得眼熟
(状态很不好
A. August
题意:微积分结论题。(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
题意:给你一个串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
题意:给一个棋盘,棋盘里有一个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
题意:一种游戏,两名玩家轮流取数。第一轮的第一个取数的玩家可以取一个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
题意:定义 (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
题意:...懒得概括了。总之是细节题,不难但是容易出错。
#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;
}