2020.7.25【算协集训】[第2次积分赛]

这次积分赛又发现一个问题,就是有时候用long long可,int反而不可;有时候int可,long long反而不可。不太清楚除了每个都试一波之外还有没有什么方法能直接确定用long long还是int的。 _ \_ _(´ཀ`」 ∠) _ \_ _

Problem A. 徐半仙的数学难题

描述
徐半仙经常修炼。但是每次修炼所能提升的功力确是不确定的(可能是功力还不够深厚吧)。
每次修炼结束之后,徐半仙的脑海中就会浮现出两个数字,n和m,他的师父跟他说他每次修炼增加的功力就是由这两个数决定的。每次增加的功力为(n!!!)%m,即n的阶乘的阶乘的阶乘对m取模之后的值。
徐半仙想让你帮他写一个程序,通过n和m得到他每次修炼之后提升了多少功力。如果这次修炼后提升的功力为0,输出baigei
输入数据
多组输入,第一行一个正整数t(1 ≤ t ≤ 105)表示数据组数 每组数据包含两个整数n,m(0 ≤ n ≤ 109,1 ≤ m ≤ 109)
输出数据
对于每组数据,如果答案为0,输出baigei,否则输出答案(每次修炼后提升的功力)

样例输入样例输出提示
2
2 6553
2 2
2
baigei
在样例中,(2!!!) = 2,对6553取模为2,直接输出,对2取模为0,输出baigei

分析

犹豫就会败北,冲动就会白给!老实人就会被孤立!(不)

这道题需要先讨论一手:

  1. n ≥ 4 n≥4 n4
    为啥要单独分 n ≥ 4 n≥4 n4 的情况,这当然是因为在下面疯狂举例子得出来的(小声bb)。实际上, 13 ! = 6 , 227 , 020 , 800 13!=6,227,020,800 13!=6,227,020,800已经爆了int的范围,也一定是已经超过 1 0 9 10^9 109 了。而 4 ! = 24 4!=24 4!=24 4 ! ! = 24 ! 4!!=24! 4!!=24! 不用说都懂了。无论 m m m 的值是多少,它总归是不能超过 1 0 9 10^9 109 的,所以 n ≥ 4 n≥4 n4 时, ( n ! ! ! ) % m (n!!!)\%m (n!!!)%m 的结果一定是 0 0 0 ——这是因为阶乘最后取模和每次取模后相乘的结果是一样的,而现在计算出来的值一定大于模,那么一定会出现一项取模的值为 0 0 0 ,因此结果一定为 0 0 0 ——直接输出 “baigei” 就vans了
  2. n = 3 n=3 n=3
    n = 3 n=3 n=3 时需要具体情况具体分析,也就是真正需要计算 ( n ! ! ! ) % m (n!!!)\%m (n!!!)%m 的值。
  3. n = 1 或 2 n=1或2 n=12
    n = 1 或 2 n=1或2 n=12 的时候,只需要计算 n % m n\%m n%m 的值是多少就可以了。这是因为 n = 1 n=1 n=1 时, n ! = 1 , n ! ! = 1 , n ! ! ! = 1 n!=1,n!!=1,n!!!=1 n!=1,n!!=1,n!!!=1 n = 2 n=2 n=2 的时候也是同理。不管是 n n n 的多少次阶乘,只要 n = 1 或 2 n=1或2 n=12 ,它的 x x x 次阶乘还是它本身。
  4. n = 0 n=0 n=0
    n = 0 n=0 n=0 的情况其实可以和 n = 1 或 2 n=1或2 n=12 的情况归纳在一起,这里单独拎出来说是因为 0 ! = 1 0!=1 0!=1 ,这一点比较特殊而已。其他直接还是像上一种情况一样,不论是 0 0 0 的多少次阶乘,答案都是 1 1 1 。因此可以直接计算 1 % m 1\%m 1%m 的值。

代码

#include<cstdio>
using namespace std;
typedef long long ll;
const ll maxn=1e9+10;
int T;
ll N,M,temp;
ll f(ll x,ll mod)	//计算 (x!!!)%mod 的值
{
	ll sum1=1,sum2=1,sum3=1;
	for(int i=2;i<=x;i++)	//第一个阶乘
	{
		sum1*=i;
	}
	for(int i=2;i<=sum1;i++)	//第二个阶乘
	{
		sum2=(sum2*(i%mod))%mod;
	}
	for(int i=2;i<=sum2;i++)	//第三个阶乘
	{
		sum3=(sum3*(i%mod))%mod;
	}
	return sum3;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld",&N,&M);
		if(N>=4)	printf("baigei\n");
		else if(N==0)
		{
			temp=1%M;
			if(temp)	printf("%lld\n",temp);
			else	printf("baigei\n");
		}
		else if(N==3)
		{
			temp=f(N,M);
			if(temp)	printf("%lld\n",temp);
			else	printf("baigei\n");
		}
		else
		{
			temp=N%M;
			if(temp)	printf("%lld\n",temp);
			else	printf("baigei\n");
		}
	}
	return 0;
}

Problem B. 徐半仙的八字谜盘

题目描述
有一天徐半仙正在街道上给人算八字,他摆弄一个棋盘,和K个棋子,棋盘是N ×N的矩阵方格图,共有 N × N 个格子。
突然一位神秘ACM大佬来了,对徐半仙说:大仙,是不是您什么难题都算得出来啊?
徐半仙:Of course!
ACM大佬:那你算算这 N × N 的棋盘,每个方格只能放一个棋子,要把K个棋子都放进去,并且第一行,最后一行,第一列,最后一列,主对角线,副对角线都至少有一颗棋子的方案数是多少?
顿时,徐半仙冷汗直流,不知如何应对这位不速之客,只好说:ok…ok…
为了不显尴尬,徐半仙偷偷用他的迷你手机给你发了一通电报,告诉你N和K,请求你尽快把答案发送给他。
输入数据
第一行为数据的组数T (T ⩽ 1000) 接下来T行,每行为两个整数N和K (2 ⩽ N ⩽ 100,0 ⩽ K ⩽ 100)
输出数据
输出T行,第i行为第i组询问的答案。由于答案可能过大,请将答案模1e9+7之后再输出。

样例输入样例输出
1
3 3
10

分析

这题主要利用了 容斥原理+组合计数 的知识。
因为我们要求出棋盘的第一行、最后一行、第一列、最后一列、主对角线、副对角线都至少有一颗棋子的方案数。如果直接正面考虑有多少个,就十分麻烦。因此我们考虑先计算任意摆放棋子的方案数,再减去不满足条件的方案数,就能得到满足条件的方案数了。

但是,在减去不满足条件的方案数的过程中,容易出现多减的现象。如:先减去了第一行没有摆放棋子的方案数,再减去了最后一行没有摆放棋子的方案数,实际上就多减去了第一行和最后一行都没有摆放棋子的方案数。为了避免这种减多了的情况,我们考虑利用容斥原理。容斥原理是奇加偶减,但是因为我们在最前面要加上一个负号(因为是减去不满足条件的方案数,而我们只是在考虑不满足条件的方案数的时候利用了容斥原理),所以呈现的状态是奇减偶加的样子。

此外,我们考虑利用二进制暴力枚举的方法,对摆放棋子的情况进行枚举。一共要考虑 第一行、最后一行、第一列、最后一列、主对角线、副对角线 这 6 6 6 条线路上的状况,共 2 6 2^6 26 种情况,对应 i ∈ [ 0 , 31 ] i∈[0,31] i[0,31] 。而对于 i i i 的二进制数,第 j j j 位如果是 1 1 1 ,就代表它对应的那一条线路上没有棋子;反之则可有可无。

(剩下的看代码应该能看懂)

最后输出的时候,如果是奇数,就需要在前面减去现在的组合数。此时一定要写成 先 + m o d +mod +mod % m o d \%mod %mod 的情况,否则会 WA 。这是因为容斥的最后结果一定是正数,但是二进制枚举的过程中可能会出现负数。当这个负数达到 m o d mod mod 的倍数时,整体结果就会直接归零,从而造成最终结果的错误。如果先 + m o d +mod +mod % m o d \%mod %mod ,就可以避免这种情况。

代码

#include<cstdio>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
const ll maxn=10010;
int T,N,K; 
ll C[10010][110],sum,cnt,n,m,l,r,total;
void initC()	//组合计数
{
	C[1][1]=1;
	for(int i=0;i<maxn;i++)	C[i][0]=1;
	for(int i=1;i<maxn;i++)
	{
		for(int j=1;j<110;j++) 
		{
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
		}
	}
}
int main()
{
	scanf("%d",&T);
	initC();
	while(T--)
	{
		scanf("%d%d",&N,&K);
		sum=0;
		for(int i=0;i<(1<<6);i++)	//二进制枚举
		{
			cnt=0,n=N,m=N,l=0,r=0;	//cnt实现容斥原理。n行、m列、l主对角线、r副对角线的可放格子个数 
			for(int j=0;j<6;j++)
			{
				if(1&(i>>j))
				{
					cnt++;
					if(j==0)	n--;	//第一行没有棋子 
					if(j==1)	m--;	//第一列没有棋子 
					if(j==2)	n--;	//最后一行没有棋子 
					if(j==3)	m--;	//最后一列没有棋子 
					if(j==4)	//主对角线没有棋子 
					{
						l=N;
						if((i>>0 & 1) || (i>>1 & 1))	l--;	//第一行和第一列没有棋子 
						if((i>>2 & 1) || (i>>3 & 1))	l--;	//最后一行和最后一列没有棋子 
					} 
					if(j==5)	//副对角线没有棋子 
					{
						r=N;
						if((i>>0 & 1) || (i>>3 & 1))	r--;	//第一行和最后一列没有棋子  
						if((i>>1 & 1) || (i>>2 & 1))	r--;	//第一列和最后一行没有棋子 
					}
				}
			}
			total=n*m;	//有total个格子可以选择被放棋子 
			if(l && r)	//主副对角线都没有棋子 
			{
				if(N & 1)	total=total-(l+r-1);	//N为奇数时,主副对角线共用了一个格子 
				else	total=total-(l+r);
			}
			else	//主副对角线至少有一个有棋子 
			{
				total=total-(l+r);
			}
			if(cnt & 1)	sum=(sum-C[total][K]+mod)%mod;	//一定要写成先+mod再%mod,不然会WA 
			else	sum=(sum+C[total][K])%mod;
		}
		printf("%lld\n",sum);
	}
	return 0;
}

Problem C. 徐半仙的修炼

描述
众所周知,徐半仙之所以成为一个手速巨佬,主要是因为他持之以恒的刷题和训练;
有一天,在他繁忙的修炼过程中,碰见了名字叫做orz的同学,orz同学非常膜拜徐半仙的切题之快,于是不停的询问半仙的修炼秘诀,徐半仙告诉他:“修炼的秘诀在于持之以恒,需要你每天一点一滴的积累经验,并且每天获得的经验是有规律的,假设第一天获得的经验是2,第二天获得的经验是5,在这以后,第i天获得的经验是第 i−1 天的经验、第 i−2 天的经验加上 i3 的总和。” orz同学听完以后若有所思,于是他开始行动了;
在修炼了n天以后,他觉得自己已经很厉害了,于是他想知道自己在第n天获得的经验是多少,作为orz的好朋友,请你告诉他答案是多少。(由于这个值可能太大,请对1e9 + 7取模)
输入数据
第一行是一个整数T(1 ⩽ T ⩽ 1000),表示样例的个数。
以后每个样例一行,是一个整数n(1 ⩽ n ⩽ 1018)。
输出数据
每个样例输出一行,一个整数,表示第n天获得的经验 (mod 1000000007)

样例输入样例输出
2
3
5
34
262

分析

矩阵快速幂的主要问题就是推导式子,只要推导出式子就完成了一大步。而今天遇到的一个问题就是竟然TL了 _ \_ _(´ཀ`」 ∠) _ \_ _改了五百年,后来发现可能是没用 long long 的锅。啊这

已知式子 f ( x ) = { 2 , x = 1 5 , x = 2 f ( x − 1 ) + f ( x − 2 ) + x 3 , x ≥ 3 f(x)=\begin{cases}2, x=1 \\ 5, x=2 \\ f(x-1) + f(x-2) + x^3, x≥3\end{cases} f(x)=2x=15x=2f(x1)+f(x2)+x3x3 ,要求出 f ( x ) % m f(x)\%m f(x)%m 的值( m = 1 e 9 + 7 m=1e9+7 m=1e9+7
推导出来的式子是这个: [ f ( n ) f ( n − 1 ) ( n + 1 ) 3 ( n + 1 ) 2 ( n + 1 ) 1 ( n + 1 ) 0 ] = [ 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 3 3 1 0 0 0 1 2 1 0 0 0 0 1 1 0 0 0 0 0 1 ] [ f ( n − 1 ) f ( n − 2 ) n 3 n 2 n 1 n 0 ] = [ 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 3 3 1 0 0 0 1 2 1 0 0 0 0 1 1 0 0 0 0 0 1 ] n − 2 [ f ( 2 ) f ( 1 ) 3 3 3 2 3 1 3 0 ] = [ 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 3 3 1 0 0 0 1 2 1 0 0 0 0 1 1 0 0 0 0 0 1 ] n − 2 [ 5 2 27 9 3 1 ] \left[ \begin{matrix} f(n) \\ f(n-1) \\ (n+1)^3 \\ (n+1)^2 \\ (n+1)^1 \\ (n+1)^0\end{matrix} \right]= \left[ \begin{matrix} 1 & 1 & 1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 3 & 3 & 1 \\ 0 & 0 & 0 & 1 & 2 & 1 \\ 0 & 0 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{matrix} \right] \left[ \begin{matrix} f(n-1) \\ f(n-2) \\ n^3 \\ n^2 \\ n^1 \\ n^0 \\ \end{matrix} \right]=\left[ \begin{matrix} 1 & 1 & 1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 3 & 3 & 1 \\ 0 & 0 & 0 & 1 & 2 & 1 \\ 0 & 0 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{matrix} \right]^{n-2} \left[ \begin{matrix} f(2) \\ f(1) \\ 3^3 \\ 3^2 \\ 3^1 \\ 3^0 \\ \end{matrix} \right]=\left[ \begin{matrix} 1 & 1 & 1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 3 & 3 & 1 \\ 0 & 0 & 0 & 1 & 2 & 1 \\ 0 & 0 & 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 & 0 & 1 \end{matrix} \right]^{n-2} \left[ \begin{matrix} 5 \\ 2 \\ 27 \\ 9 \\ 3 \\ 1 \\ \end{matrix} \right] f(n)f(n1)(n+1)3(n+1)2(n+1)1(n+1)0=110000100000101000003100003210001111f(n1)f(n2)n3n2n1n0=110000100000101000003100003210001111n2f(2)f(1)33323130=110000100000101000003100003210001111n25227931

代码

#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=6;
const ll m=1e9+7;
ll n,sum;
struct node
{
	ll a[N][N];
}res,ans;	//res是初始矩阵,ans是结果矩阵
node mul(node a,node b)
{
	node temp;
	memset(temp.a,0,sizeof(temp.a));
	for(ll i=0;i<N;i++)
	{
		for(ll j=0;j<N;j++)
		{
			for(ll k=0;k<N;k++)
			{
				temp.a[i][j]=(temp.a[i][j]+a.a[i][k]*b.a[k][j])%m;
			}
		}
	}
	return temp;
}
void qpower(ll a)
{
	memset(ans.a,0,sizeof(ans.a));
	for(ll i=0;i<N;i++)
	{
		ans.a[i][i]=1;
	}
	while(a)
	{
		if(a&1)	ans=mul(ans,res);
		res=mul(res,res);
		a>>=1;
	}	
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld",&n);
		res.a[0][0]=1,res.a[0][1]=1,res.a[0][2]=1,res.a[0][3]=0,res.a[0][4]=0,res.a[0][5]=0;
		res.a[1][0]=1,res.a[1][1]=0,res.a[1][2]=0,res.a[1][3]=0,res.a[1][4]=0,res.a[1][5]=0;
		res.a[2][0]=0,res.a[2][1]=0,res.a[2][2]=1,res.a[2][3]=3,res.a[2][4]=3,res.a[2][5]=1;
		res.a[3][0]=0,res.a[3][1]=0,res.a[3][2]=0,res.a[3][3]=1,res.a[3][4]=2,res.a[3][5]=1;
		res.a[4][0]=0,res.a[4][1]=0,res.a[4][2]=0,res.a[4][3]=0,res.a[4][4]=1,res.a[4][5]=1;
		res.a[5][0]=0,res.a[5][1]=0,res.a[5][2]=0,res.a[5][3]=0,res.a[5][4]=0,res.a[5][5]=1;
		if(n==1)	printf("%d\n",2);
		else if(n==2)	printf("%d\n",5);
		else
		{
			qpower(n-2);
			sum=0;
			sum=(sum+ans.a[0][0]*5%m)%m;
			sum=(sum+ans.a[0][1]*2%m)%m;
			sum=(sum+ans.a[0][2]*27%m)%m;
			sum=(sum+ans.a[0][3]*9%m)%m;
			sum=(sum+ans.a[0][4]*3%m)%m;
			sum=(sum+ans.a[0][5]*1%m)%m;
			printf("%lld\n",sum%m);
		}
	}
	return 0;
}

Problem D. 徐半仙的修仙之路

描述
徐半仙每天闲来无事就在家中打坐,他相信这样能够悟道,得道升仙的方法;
有一天他接到了一个电话,电话中告诉他,蜀山的掌门给了他一次让他去蜀山学习的机会,约他去城郊见面。于是他特别开心的答应了下来,但是他到了以后发现对方竟然是传销团伙,徐半仙这时十分的后悔,他想偷偷的逃走但是这里守卫森严他一点办法都没有,这时他十分无助的哭泣着并且十分后悔自己迷恋修仙。忽然旁边的憨憨龙告诉他,只要你能够计算出这个房子有几间房间,并且最大的一间房间有多大就可以帮助他逃走了。
这个房子是只有一层,被分成了n行m列,每一间房间的面积都是1,但是因为传销团伙为了节约空间,这些房间的四面墙有些被堵着了。并且如果两个房间连在一起就是一个面积为2的房间。
这时徐半仙突然看到了机会,于是经过徐半仙的一波神奇的操作后他和憨憨龙一起逃出了这个传销团伙,并且还配合警方抓捕了这个传销团伙。从这以后徐半仙再也不想着修仙了,他一心专注于程序设计,并成为了一名优秀的acmer。
聪明的你一定想知道徐半仙如何计算的吗?那么你也来尝试一下吧,看看你和徐半仙谁更厉害一点!
输入数据
每个测试有T组数据每组第一行输入两个数 n,m (1 ⩽ n,m ⩽ 50。接下来n行数据,每行m个整数,这个整数p表示这个房间所拥有的墙的编号和 (1表示左墙,2表示上墙,4表示右墙,8表示下墙)
输出数据
输出两个整数分别代表房间的数量,和最大的房间面积。

样例输入样例输出样例解释
1
4 7
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13
5 9样例所描述的房间为下面形状,# 表示该方向有墙,其他符号只表示没有墙。比如说 11 左上角第一个数,由 1 2 8 之和是 11 表示只有左墙,上墙,下墙.
这里五种颜色表示5个房间,最大的房间面积为9;

分析

由题意我们可以知道:输入的每个整数 p p p 的含义是这个房间拥有的墙的编号和
左墙是 1 1 1 ,二进制数为 0001 0001 0001
上墙是 2 2 2 ,二进制数为 0010 0010 0010
右墙是 4 4 4 ,二进制数为 0100 0100 0100
下墙是 8 8 8 ,二进制数为 1000 1000 1000

编号和为 11 11 11 ,它的二进制表示为 1011 1011 1011 ,封住了左上下墙,只能向右走。
编号和为 9 9 9 ,它的二进制表示为 1001 1001 1001 ,封住了左下墙,能向右或向上走。
……

举例我们可以发现以下规律:

  • 如果 ( p & 1 = = 0 ) ( p \& 1 ==0) (p&1==0) ,说明可以向左走。
  • 如果 ( p & 2 = = 0 ) ( p \& 2 ==0) (p&2==0) ,说明可以向上走。
  • 如果 ( p & 4 = = 0 ) ( p \& 4 ==0) (p&4==0) ,说明可以向右走。
  • 如果 ( p & 8 = = 0 ) ( p \& 8 ==0) (p&8==0) ,说明可以向下走。

然后用 d f s dfs dfs 做就可以了。

注意: i f ( ( a [ x ] [ y ] & 1 ) = = 0 ) if((a[x][y] \& 1)==0) if((a[x][y]&1)==0) 中的 ( a [ x ] [ y ] & 1 ) (a[x][y] \& 1) (a[x][y]&1) 的括号一定要加一定要加一定要加

代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=60;
int T,m,n; 
int roomnum,maxroom,room;	//roomnnum记录房间个数,maxroom记录已有最大房间面积,room记录当前房间面积
int a[maxn][maxn];
bool bj[maxn][maxn];
void dfs(int x,int y)
{
	if(bj[x][y])	return;
	room++;
	bj[x][y]=true;
	if((a[x][y] & 1)==0)	dfs(x,y-1);	//里面的括号一定要加!!
	if((a[x][y] & 2)==0)	dfs(x-1,y);
	if((a[x][y] & 4)==0)	dfs(x,y+1);
	if((a[x][y] & 8)==0)	dfs(x+1,y);
}
int main()
{
	cin>>T; 
	while(T--)
	{
		cin>>n>>m;
		memset(bj,false,sizeof(bj));
		roomnum=maxroom=0;
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<m;j++)
			{
				cin>>a[i][j];
			}
		}
		for(int i=0;i<n;i++)
		{
			for(int j=0;j<m;j++)
			{
				if(!bj[i][j])
				{
					room=0;
					roomnum++;	//房间数增加
					dfs(i,j);
					maxroom=max(maxroom,room);
				}
			}
		}
		cout<<roomnum<<" "<<maxroom<<endl;
	}
	return 0;
}

Problem E. 徐半仙的热心肠

描述
众所周知,徐半仙除了修炼,摆摊等等之外,他还非常地乐于助人。某天有位初中生向他请教一道数学题目,但是他正处于修炼的关键时期不能分心,所以他就来拜托你帮助此学生解答一下。问题如下:
给出科学计数法表示的实数A,将其转化为普通十进制表示法,并保证所有有效位都被保留。科学计数法表示格式为:[ + − +− +][1−9].[0−9] + E[ + − +− +][0−9],即数字的整数部分只有1位小数部分至少有1位,该实数及其指数部分的正负号都明确给出。
输入数据
在一行中给出科学计数法表示的实数A。该字符串总长度不超过10000,且其指数的绝对值不超过10000。
输出数据
在一行中按普通十进制表示法输出A,所有有效位均须被保留,包括末尾的0。

样例输入1样例输出1
+5.43210E-20.0543210
样例输入2样例输出2
-2.1E+10-21000000000

分析

(分析前的bb)下面分析的内容是老实人的做法!还有简单到让人想发黑人问号❓的做法+利用字符串处理函数的做法等等。字符串本来我也就不太会,也懒得记那些函数,这里就不讲了。然后简单的做法只在代码中讲述,分析中不再详细解释了。

这题需要注意:对于实数来说,整数部分只有一位,小数部分至少一位;对于指数来说,它一定是整数。

因为指数有正有负,因此我们需要分类讨论情况。

  1. 指数是正数(小数点需要向右移)
    右移时也分为两种情况:

    1. 已有的小数个数小于等于指数——移动后不会有小数点,且可能需要在后面补零;

    2. 已有的小数个数大于指数——移动后还会有小数点,只需要注意小数点的位置。

    这两种情况如图举例:(因为实数符号位一开始我们就已经输出完了,因此后面就不会再对数据产生什么影响,也不需要考虑了,所以这里没有区分正负号)

    标记 E E E 的下标是 b j bj bj ,指数大小为 s u m sum sum ,那么小数的个数就是 b j − 3 bj-3 bj3 0 0 0 是符号位, 1 1 1 是实数的整数部分, 2 2 2 是小数点位置)。
    对于第①种情况来说,需要忽略小数点原样输出,输出完毕后需要在后面补上 s u m − b j + 3 sum-bj+3 sumbj+3 个零。
    对于第②种情况来说,除了第 s u m + 2 sum+2 sum+2 位不仅要输出数需要输出小数点外,其他就只需要忽略小数点原样输出就可以。( 2 2 2 是原先的小数点位置)

  2. 指数是负数(小数点需要向左移)
    此时需要将小数点往左移,并且在前面补上零,后面忽略小数点原样输出。如图举例:
    要补的零的总个数是 s u m sum sum ,但是第一个 0 0 0 还需要输出小数点,其他只需要补零后忽略小数点原样输出就可以。

代码

  1. 老实人的代码
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const ll maxn=10010;
    char c,str[maxn];
    int len,bj;
    ll sum;
    int main()
    {
    	scanf("%s",&str);
    	len=strlen(str);
    	if(str[0]=='-')	printf("-");
    	sum=0;
    	for(int i=1;i<len;i++)
    	{
    		if(str[i]=='E')
    		{
    			bj=i;	//标记一下E的位置
    			for(int j=bj+2;j<len;j++)	//计算指数的大小
    			{
    				sum=sum*10+str[j]-'0';
    			}
    		}
    	}
    	if(str[bj+1]=='+')	//要向右移小数点
    	{
    		if(bj-3<=sum)	//已有的小数个数小于等于指数
    		{
                for(int i=1;i<bj;i++)	//只需要忽略小数点全部输出
    			{
                    if(str[i]!='.')
    				{
    					printf("%c",str[i]);
    					
    				}
                }
                for(int i=1;i<=sum-bj+3;i++)	//在后面补零。sum-bj+3是去掉原先的小数个数后还需要补的零的个数
                {
                	printf("0");
    			}
            }
    		else	//已有的小数个数大于指数
    		{
                for(int i=1;i<bj;i++)
    			{
                    if(str[i]!='.' && i!=sum+2)
    				{
    					printf("%c",str[i]);
    				}
                    if(i==sum+2)
    				{
    					printf("%c.",str[i]);
    				}
                }
            }
    	}
    	else if(str[bj+1]=='-')	//要向左移小数点
    	{
    		for(int i=1;i<=sum;i++)
    		{
                if(i==1)	printf("0.");
                else	printf("0");
            }
            for(int i=1;i<bj;i++)
    		{
                if(str[i]!='.')	printf("%c",str[i]);
            }
    	}
    	return 0;
    }
    
  2. 简单的做法(城会玩系列)
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const ll maxn=10010;
    char c,xs[maxn];
    int zs,e;
    int main()
    {
    	scanf("%c%d.%[0-9]E%d",&c,&zs,xs,&e);	//%[0-9]意为只读取包含0-9字符的字符串,遇到了其他字符就停止
    	if(c=='-')	printf("-");
    	if(e<0)	//指数为负数,要左移小数点 
    	{
    		printf("0.");
    		while(++e)	printf("0");
    		printf("%d%s",zs,xs);
    	}
    	else	//指数为正数,要右移小数点 
    	{
    		printf("%d",zs);
    		int len=strlen(xs);
    		for(int i=0;i<e || i<len;i++)
    		{
    			if(i==e)	printf(".");
    			if(i<len)	printf("%c",xs[i]);
    			else	printf("0");
    		}
    	}
    	return 0;
    }
    

Problem F. 徐半仙学会了火眼金睛

描述
星期天徐半仙要和他村的何仙姑玩纸牌游戏,游戏的规则很简单,两人各有一副相同的纸牌,每副纸牌一共有n张,纸牌上面有数字ai(1 ⩽ ai ⩽ 1e6)表示纸牌的大小,可能会有相同大小的纸牌。每回合两人各拿出一张牌,谁的牌较大,谁就取得一次胜利,一共可以比n轮。这本来是一个很公平,靠运气的游戏。但由于徐大仙最近一直在家修炼,学会了火眼金睛,他可以从纸牌的反面看到纸牌的大小,因此他可以在看到徐半仙要拿出来的牌后,决定自己要拿哪 一张牌。他想知道如果自己不失误的话,最多能获得多少次胜利,所以想请学习算法的你帮帮他,如果你能帮助他,他可以教给你怎么修炼火眼金睛。
输入数据
第一行为纸牌的数量n(1 ⩽ n ⩽ 1e6) 第二行为n个正整数ai(1 ⩽ ai ⩽ 1e6)表示纸牌的大小。
输出数据
一行数字,最多能取得多少次胜利。

样例输入样例输出
5
1 2 3 4 5
4
样例输入样例输出
5
1 1 1 1 1
0

分析

两个人的牌是一样的,只是徐半仙可以根据对方出的牌来选择自己出的牌,因此可以直接在同一个数组a中进行比较。就像是田忌赛马这个故事一样,只需要刚刚比对方出的牌大一点就可以了。当何仙姑出牌出的已经是最小的牌时,结束游戏。

这题也有很多种写法,这里详细讲一下第三种写法:

  1. 当纸牌数字无重复时
    无重复的时候比较好判断。纸牌数量是 n n n ,结果就是 n − 1 n-1 n1(最大的那个数字不能赢)

  2. 当纸牌数字有重复时

    1. 只有一个数字重复
      例如:纸牌数字为 1 , 2 , 2 , 2 , 3 1,2,2,2,3 1,2,2,2,3 ,对应出的牌为 2 , 3 , 1 , 2 , 2 2,3,1,2,2 2,3,1,2,2 。假设重复 m m m 次,就相当于有 m − 1 m-1 m1 次不能赢,再加上最大的那个数字一定不能赢,因此一共能赢 n − ( m − 1 ) − 1 = n − m n-(m-1)-1=n-m n(m1)1=nm 次。
    2. 有多个数字重复(以两个数字重复为例)
      例如:纸牌数字为 1 , 2 , 2 , 2 , 3 , 3 1,2,2,2,3,3 1,2,2,2,3,3 ,对应出的牌为 2 , 3 , 3 , 1 , 2 , 2 2,3,3,1,2,2 2,3,3,1,2,2 。我们可以发现:当有多个重复数字时,只有重复次数最多的那个起作用,能赢的次数还是 n − m n-m nm m m m 是重复次数最多的那个数字的重复次数)。

综上所述:这题的答案是 n − m n-m nm n n n 是纸牌数量, m m m 是重复次数最多的那个数字的重复次数)。

代码

  1. 第一种写法

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6+10;
    int n,a[maxn];
    bool bj[maxn];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=0;i<n;i++)
    		scanf("%d",&a[i]);
    	sort(a,a+n);
    	int j=n-2,sum=0;
    	for(int i=n-1;j>=0;i--)	//从大往小搜
    	{
    		while(a[i]==a[j])	//a[i]是徐半仙出的牌,a[j]是何仙姑出的牌
    		{
    			j--;
    			if(j==0)	//已经是最小的牌了(这句是为了避免出界,如不加可能会判断a[-1])
    				break;
    		}
    		if(a[i]>a[j])
    			sum++;
    		j--;
    	}
    	printf("%d\n",sum);
    	return 0;
    }
    
  2. 第二种写法

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6+10;
    int n,a[maxn];
    int main()
    {
    	scanf("%d",&n);
    	for(int i=0;i<n;i++)
    		scanf("%d",&a[i]);
    	sort(a,a+n);
    	int j=0,sum=0;
    	for(int i=0;i<n;i++)
    	{
    		if(a[i]>a[j])
    		{
    			sum++;
    			j++;
    		}
    	}
    	printf("%d\n",sum);
    	return 0;
    }
    
  3. 第三种写法

    #include<cstdio>
    #include<map>
    using namespace std;
    typedef long long ll;
    const int maxn=1e6+10;
    int n,m,a[maxn];
    map<int,int> mp;
    int main()
    {
    	scanf("%d",&n);
    	for(int i=0;i<n;i++)
    	{
    		scanf("%d",&a[i]);
    		mp[a[i]]++;
    		m=max(m,mp[a[i]]);
    	}
    	printf("%d\n",n-m);
    	return 0;
    }
    

Problem G. 徐半仙的数学问题

描述
已知有a,b,c,d四个正整数求满足下列两个等式的x的个数。
gcd(x,a) = b
lcm(x,c) = d
输入数据
第一行输入一个整数T,表示测试的组数
第2行到T+1行,每行4个整数,分别表示a,b,c,d.数据保证a能够被b整除,d能够被c整除
1 <= T <= 2000
1 <= a,b,c,d <= 2e9
输出数据
共T行,每行一个整数,如果不存在这样的x,输出0,否则,输出满足条件的x的个数

样例输入样例输出
4
41 1 96 288
95 1 37 1776
8481 1 999976991 1999953982
32560 2 999992632 1999985264
6
2
4
0

分析

题目中有两个式子: { g c d ( x , a ) = b ⋯ ⋯ ① l c m ( x , c ) = d ⋯ ⋯ ② \begin{cases}gcd(x,a)=b\cdots\cdots①\\lcm(x,c)=d\cdots\cdots②\end{cases} {gcd(x,a)=blcm(x,c)=d 由此我们可以知道: x x x b b b 的整数倍,且 x x x d d d 的因子。此外,我们还需要分析一手:

  1. 对于式①( g c d ( x , a ) = b gcd(x,a)=b gcd(x,a)=b):
    g c d ( x , a ) = b ⇒ { x = k 1 ∗ b a = k 2 ∗ b ⇒ g c d ( k 1 , k 2 ) = 1 ⇒ g c d ( x / b , a / b ) = 1 gcd(x,a)=b \Rightarrow \begin{cases}x=k_1*b\\a=k_2*b\end{cases} \Rightarrow gcd(k_1,k_2)=1\Rightarrow gcd(x/b,a/b)=1 gcd(x,a)=b{x=k1ba=k2bgcd(k1,k2)=1gcd(x/b,a/b)=1 .
    推广的结论:如果有 g c d ( x , y ) = z gcd(x,y)=z gcd(x,y)=z ,则有 g c d ( x / z , y / z ) = 1 gcd(x/z,y/z)=1 gcd(x/z,y/z)=1 .
  2. 对于式②( l c m ( x , c ) = d lcm(x,c)=d lcm(x,c)=d):
    g c d ( x , c ) = x ∗ c / l c m ( x , c ) = x ∗ c / d gcd(x,c)=x*c/lcm(x,c)=x*c/d gcd(x,c)=xc/lcm(x,c)=xc/d .
    由推广的结论得: g c d ( x , c ) = x ∗ c / d ⇒ g c d ( x / ( x ∗ c / d ) , c / ( x ∗ c / d ) ) = 1 ⇒ g c d ( d / c , d / x ) = 1 gcd(x,c)=x*c/d \Rightarrow gcd(x/(x*c/d),c/(x*c/d))=1 \Rightarrow gcd(d/c,d/x)=1 gcd(x,c)=xc/dgcd(x/(xc/d),c/(xc/d))=1gcd(d/c,d/x)=1

综上所述:式子可以化为 { g c d ( x / b , a / b ) = 1 g c d ( d / c , d / x ) = 1 \begin{cases}gcd(x/b,a/b)=1\\gcd(d/c,d/x)=1\end{cases} {gcd(x/b,a/b)=1gcd(d/c,d/x)=1 .

由此我们可以枚举 d d d 的因子,看看是否符合以上两个式子,符合则 s u m + + sum++ sum++

代码

#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int t,a,b,c,d,x,sum;
int gcd(int a,int b)
{
	return b==0?a:gcd(b,a%b);
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d",&a,&b,&c,&d);
		sum=0;
		int p=a/b,q=d/c;
		for(int x=1;x*x<=d;x++)
		{
			if(d%x==0)
			{
				if(x%b==0 && gcd(x/b,p)==1 && gcd(q,d/x)==1)	sum++;
				int y=d/x;	//d的另一个因子
				if(x==y)	continue;	//避免重复计算相同因子
				if(y%b==0 && gcd(y/b,p)==1 && gcd(q,d/y)==1)	sum++;
			}
		}
		printf("%d\n",sum);
	}
	return 0;
}

Problem H. 徐半仙的圆

描述
徐半仙每天在家无聊到划圆圈,突然他想到了一个问题,一个圆上有2N个不同的点,每次选择两个点连成一条线段,一共连成N条线段,求所有线段不相连的概率。
输入数据
第一行为n(1 ⩽ n ⩽ 1000000)
输出数据
输出:答案最简可以表示为p/q,则需要输出p ∗ * inv(q),mod=1e9+7;输出整数x满足 0≤x<M且 x*q ≡ p(mod M)。

样例输入样例输出
2666666672

分析

这题需要用到卡特兰数的知识,为啥要用这个可以参考链接
分子用到了卡特兰数的公式: f ( n ) = ( 2 n ) ! ( n + 1 ) ! n ! f(n)=\frac{(2n)!}{(n+1)!n!} f(n)=(n+1)!n!(2n)!
而分母需要用到组合计数:一开始在 2 n 2n 2n 个点里随便选两个点,然后在 2 n − 2 2n-2 2n2 个点里随便选两个点……最后剩下 2 2 2 个点里选两个点。但是这样的计算过程中会出现重复,如在算第一个 C 2 n 2 C_{2n}^2 C2n2 时,因为只能选一条边,所以我们要除以 n n n 。以此类推,分母的公式就是: S ( n ) = ( 2 n ) ! 2 n n ! S(n)=\frac{(2n)!}{2^nn!} S(n)=2nn!(2n)!
分子和分母相除,则得到公式: a n s = 2 n ( n + 1 ) ! ans=\frac{2^n}{(n+1)!} ans=(n+1)!2n 。这个公式的形式是 ( p / q ) % m o d (p/q)\%mod (p/q)%mod ,可以写成 ( p ∗ q − 1 ) % m o d (p*q^{-1})\%mod (pq1)%mod p ∗ q p*q pq 的逆元 )。 q q q 的逆元的计算方法是 q m o d − 2 q^{mod-2} qmod2

代码

#include<cstdio>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll qpow(ll x,ll y)	//快速幂
{
	ll res=1;
	while(y)
	{
		if(y&1)	res=(res*x)%mod;
		x=(x*x)%mod;
		y>>=1;
	}
	return res;
}
ll n,sum,inv;
int main()
{
	scanf("%lld",&n);
	sum=inv=1;
	for(int i=1;i<=n;i++)
	{
		sum=sum*2%mod;
	}
	for(int i=1;i<=n+1;i++)
	{
		inv=inv*i%mod;
	}
	sum=(sum*qpow(inv,mod-2))%mod;
	printf("%lld\n",sum);
	return 0;
}

Problem I. 徐半仙行云布雨

描述
X地区发生了严重的干旱,久旱不雨,到处黄沙弥漫,民不聊生。一日,徐半仙途径此地,看到此情此景,十分不忍。于是便决定行云布雨,解决这场旱灾。徐半仙施展法力,顷刻之间,便下起了大雨。
但是美中不足的是农田出现了多处水洼。徐半仙决定解决掉这些水洼。徐半仙的法术每次只能解决掉一个水洼,请问徐半仙解决掉这些水洼要施展几次法术。
输入数据
输入第一行为两个正整数n和m,(1<=n,m,<=1000) 接下来输入一个n行m列的矩阵,’.'表示农田,‘W’(大写)表示水洼。
输出数据
输出徐半仙需要施展法术的次数

样例输入样例输出提示
3 3
WWW
.W.
W.W
3
上下左右相连的W为同一个水洼。如图所示,三种不同颜色的线代表三个水洼。

分析

这题就和dfs的模板题(Oil Deposits)差不了哪里去,看下代码应该能看明白辽。

当时提到Oil Deposits的题解链接

代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn=1010;
int m,n; 
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1};
char a[maxn][maxn];
void dfs(int x,int y)
{
	a[x][y]='.';
	for(int i=0;i<4;i++)
	{
		int xx=x+dx[i],yy=y+dy[i];
		if(xx>=0&&x<n && yy>=0&&y<m && a[xx][yy]=='W')
		{
			dfs(xx,yy);
		}
	}
}
int main()
{
	cin>>n>>m;
	int sum=0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			cin>>a[i][j];
		}
	}
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			if(a[i][j]=='W')
			{
				sum++;
				dfs(i,j);
			}
		}
	}
	cout<<sum<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值