2020.9.7华为算法岗笔试题(前两道)

1.题目

给一组正整数(小于100个),从第一个数开始,可以走1<=step<=len/2步,从第二步开始,必须走该位置所对应的数的步长,问最少几步可以走到最后一个位置,如果不能输出-1.

测试用例:7 5 9 4 2 6 8 3 5 4 3 9

答案:2

用例解析:第一步可以走步长2,走到位置arr[2]=9,然后走了9步,到达最后一个位置。

思路:直接暴力,可能不是最优的解法。

循环第一步所有的可能,然后,每次都申请两个临时变量,此时的位置和此时走了多少步,还有一个全局变量最小的步数,初始化101.

代码:

#include
#include
#include
#include
using namespace std;
int main()
{
	string s;
	getline(cin, s);
	istringstream ss(s);//处理输入,因为没有给数组具体是多大,所以当做字符串输入了
	vectorarr;
	int num;
	while (ss >> num)
	{
		arr.push_back(num);
	}
	int minstep=101;//全局变量,保存所有情况的最小值
	for (int j = 1; j < arr.size()/2; j++)//第一步所有情况,1,2,...len/2-1
	{
		int step = j;//保存当前的位置
		int res = 1;//此时已经走了第一步
		while (step < arr.size()-1)//当前的位置到最后一个或者超过范围,退出循环
		{
			step += arr[step];
			res++;
		}
		if (step == arr.size() - 1)当前的位置到最后一个
		{
			if (minstep >res)//更新最小步骤
			{
				minstep = res;
			}
		}
	}
	if (minstep == 101)//如果没更新过,则输出-1
	{
		cout << -1;
	}
	else
	{
		cout << minstep;
	}
	system("pause");
	return 0;
}

2.给n种颜色,m个点组成的环,如果旋转或反转不变的话则算1种,求一共多少种,结果对1e9+7求余。

测试用例:3,3

输出10

10种包括111,222,333,112,122,113,133,223,233,123.

这题一直是0%,后来知道理解错了题意,据大佬说是Polya定理,今天太晚了。明天学会了更,先放两个链接

https://blog.csdn.net/OneLine_/article/details/81389419

https://www.cnblogs.com/Memory-of-winter/p/10202117.html

-----------------------------2019.09.16----------------------------------------------------------------

我我我终于来更新了,鸽了太久了。因为我太懒了,还有我太笨了,看了好久一知半解的,收到了明天的面试通知,赶紧把自己理解的写写吧,可能有很多错误,望指正。

1.首先写一下置换群的概念:一般我们在网上看到的都是下面这张图(盗个图)

关于置换群的性质什么的我不讲了,也讲不明白,其实置换的意思就是改变位置,

比如图里的置换就是1-->a1,2-->a2,.......即把1换到a1,2换到a2的转换。

2.不动点:不动点就是指经过一种置换之后,和原来的染色方案相同,此时这种方案就叫做这种置换的不动点。

3.在Polya定理定理中还涉及到了循环节的概念,就是将这种情形(1 2 3...i....n)转换成(...)(...)这种形式,有几个括号就有几个循环节。这个概念我也看了好久,主要是通过下面这个链接懂的,虽然我认为他的举例有点小问题(哈哈,我不知道是不是我理解的问题)

https://blog.csdn.net/RaAlGhul/article/details/51767941

这篇博客中给的例子是:把6,3,4,2,1,5,最终要把它变成1,2,3,4,5,6

画一下就是6-->1,3-->2,4-->3,2-->4,1-->5,5-->6

再转换一下就是6-->1-->5-->6, 3-->2-->4-->3也就是说其实循环节就是(6 1 5)(3 2 4)。也就是说这种置换有两个循环节。

以上三个概念懂了,就可以看Burnside引理与Polya定理了。

首先先介绍Burnside引理。可以用一句话概括就是:

对于一个置换f,若一个染色方案s经过置换后不变,称s为f的不动点。将f的不动点数目记为C(f),则可以证明等价类数目为所有C(f)的平均值。

摘自:https://blog.csdn.net/liangzhaoyang1/article/details/72639208

这篇博客写的比较通俗易懂,并且将百度百科的例子解释的比较详细。总结的公式为:

l=\frac{\sum_{f=1}^{g}c(f))}{\left | G \right |}.

看例题吧,我再说下我的理解。用两种颜色对2*2的方格染色问题。(图片都是直接粘的),旋转后的本质方案

2020.9.7华为算法岗笔试题(前两道)_第1张图片

一共16种情况。

这里的置换就是指旋转的方案。有四种。

第一种:不动,16种方案,所以不动点为16

第二种:顺时针旋转90,只有1和2不变,所以不动点为2

第三种:逆时针旋转90,只有1和2不变,所以不动点为2.

第四种:旋转180度,只有1,2,11,12不变,所以不动点为4

最后结果为result=(16+2+2+4)/4=6种。

上面理解起来还算简单,但是遇到很大的数据每次枚举不动点的个数也是不现实的,然后Polya定理就是用来直接计算不动点的个数的!总结如下:(还是摘自上面的链接)

假设一个置换有k个循环,易知每个循环对应的所有位置颜色需一致,而任意两个循环之间选什么颜色互不影响。因此,如果有m种可选颜色,则该置换对应的不动点个数为^{m^{k}}。用其替换burnside引理中的C(f),即C(f)=^{m^{k}}。得到等价类数目为:

l=\frac{\sum_{f=1}^{g}c(f))}{\left | G \right |}=\frac{\sum_{f=1}^{g}m^{k}}{\left | G \right | },这里的k就是每次置换的循环节,循环节什么意思不懂再看看上面的举例吧。

这样,再看例题。

四个方格,1,2,3,4.

第一种不动:(1-->1)(2-->2)(3-->3)(4-->4),4个循环节

第二种旋转1个位置,1-->2-->3-->4-->1,即(1,2,3,4),1个循环节

第二种旋转2个位置,1-->3-->1,2-->4-->2,即(1,3)(2,4)两个循环节

第三种旋转3个位置,1-->4,2-->1,3-->2,4-->3,即1-->4-->3-->2-->1(1,4,3,2)一个循环节。

所以最后结果:result=(2^4+2^1+2^2+2^1)/4=6.

以上都理解的话就可以看本题的思路了(都是大神写的,我主要写自己的理解)

主要参考:https://blog.csdn.net/so_so_y/article/details/76691630

本题是n种颜色,m个珠子。

把公式再写一遍,l=\frac{\sum_{f=1}^{g}c(f))}{\left | G \right |}=\frac{\sum_{f=1}^{g}m^{k}}{\left | G \right | },所有置换的不动点数/置换种数。

本题包含了两大种置换,旋转和反转。

先讨论旋转:既然有m个珠子,那代表他就有0-m-1次旋转。

第一种旋转0个位置,那么就有(1)(2)(m),m个循环节。

第二种旋转1个位置,那么(1-->2-->3--....-->m-->1)1个循环节。

....依次类推,当然不是枚举,而是要找规律。

当第k种循环时,原来第i个位置的珠子到了(i+k)%n的位置了,为什么要对n求余,是因为这是一个圈,当走过一圈的时候要从0开始重新计数。下面附张图理解一下。

2020.9.7华为算法岗笔试题(前两道)_第2张图片

 

图丑,字丑哈哈。

继续说旋转k个位置时,i与(i+k)%n,(i+2k)%n,...(i+t*k)%n的珠子的位置颜色相同才能构成不动点,显然,最后转回了i位置。

即t*k%n=0,(这个时候最后转(i+tk)%n=i,才能转回位置i),所以t*k是n的倍数,所以t=n/gcd(n,k).说实话这个怎么推出来我还是有点绕,先记着吧。(gcd(n,k)表示n和k的最大公约数)

就构成了构成了 i —>(i+k)%n —> (i+2*k)%n —>··· —>i 这样一个轨迹。每个轨迹的珠子包含的珠子的数量即由i绕回i需要的数量,即上面求出t.所以循环节就等于n/t=gcd(n,k).

所以旋转的情况就解决啦:程序如下,主要参考下面这位大佬

https://www.cnblogs.com/GXZlegend/p/8296673.html

先放个最大公约数的程序:(辗转相除法求最大公约数)

int gcd(int m, int k)//辗转相除法
{
	if (k == 0)
	{
		return m;
	}
	else
	{
		return gcd(k, m%k);
	}
}

旋转情况的方案数量:

int n, m;//n为颜色数,m为珠子数
cin >> n >> m;
int d;//最大公约数
long long res = 0;
for (int i = 0; i < m; i++)
{
	long long t = 1;
	d = gcd(m,i);
	while (d--)
	{
		t *= n;
	}
	res += t;
	res %= mo;
}

接着分析翻转,翻转的情况简单一点。但是要分奇偶讨论。(参考链接与旋转是同一个)

当珠子的个数m是奇数个的时候,只能以某个珠子和圆中心为对称轴转换,那么每次置换后有m/2+1个循环节,而一共有m个对称轴可以选择。

当m为偶数时,以两个珠子的连线为对称轴时,两个珠子不动,其他的翻转,那么每次置换后有2+(m-2)/2=m/2+1个循环节,而一共有m/2个对称轴可以选择。还可以以两个珠子之间的空隙为对称轴,两两翻转,那么每次置换后有m/2个循环节,而一共有m/2个对称轴可以选择(给个丑图,图里n写错了,应该是m,习惯用n代表个数了)

2020.9.7华为算法岗笔试题(前两道)_第3张图片

 

综上 ,翻转的代码如下:

if (m & 1)//奇数
	{
		long long t = m;
		for (int i = 1; i <= m/2+1; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
	}
	else
	{
		long long t = m / 2;
		for (int i = 1; i <= m / 2 + 1; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
		t = m / 2;
		for (int i = 1; i <= m / 2; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
	}

最后还得除以所有的置换数旋转是m种,翻转不管是奇数还是偶数也都是m种,所以最后整体代码就是:

#include

using namespace std;

int mo = 1e9;

int gcd(int m, int k)//辗转相除法
{
	if (k == 0)
	{
		return m;
	}
	else
	{
		return gcd(k, m%k);
	}
}
int main()
{
	int n, m;//n为颜色数,m为珠子数
	cin >> n >> m;
	int d;//最大公约数
	long long res = 0;
	for (int i = 0; i < m; i++)
	{
		long long t = 1;
		d = gcd(m,i);
		while (d--)
		{
			t *= n;
		}
		res += t;
		res %= mo;
	}
	if (m & 1)//奇数
	{
		long long t = m;
		for (int i = 1; i <= m/2+1; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
	}
	else
	{
		long long t = m / 2;
		for (int i = 1; i <= m / 2 + 1; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
		t = m / 2;
		for (int i = 1; i <= m / 2; i++)
		{
			t *= n;
		}
		res += t;
		res %= mo;
	}
	cout << res / 2 / m;
	system("pause");
	return 0;
}

其实这道题貌似就是POJ2409原题,可以百度一下(我不是很确定)

然后结合这道题给的例子和2409的例子看一下输出;

答案应该是正确的。

以上是我的理解,也不知道有没有说错误导别人的地方,还望指正。希望明天幸运一点!

 

 

你可能感兴趣的:(2020.9.7华为算法岗笔试题(前两道))