郑州大学第八届校赛正式赛题解

就是模拟转一个二阶魔方,魔方有个性质就是连续转一个特定的序列,然后就会在某个次数后回到原来的状态,不过阶数越大,转的次数越多,二阶次数就很小了。
对于这个问题,用一个二维数组来表示一个面,每次操作,就按照转的方式更改数组,直到回到初始状态,我们假定初始状态是一个复原了的二阶魔方。
我这里写的是用了一个结构体表示了一个面,0-5号结构体分别表示前后上下左右。
对于前后左右四个面,你面对这四个面的时候,a[0][0],a[0][1],a[1][0],a[1][1]分别表示左上,右上,左下,右下四个格子的颜色;对于上下两个面,当你面朝魔方前这个面的时候,俯瞰上下两个面,a[0][0],a[0][1],a[1][0],a[1][1]分别表示左上,右上,左下,右下方格的颜色。
有了表示,下面就是写旋转的部分了,细心一点儿就行了。
代码如下:
#include <cstdio>
#include <cstring>

using namespace std;

#define N 15

struct Face {
	int a[2][2];
	void set(int val)
	{
		for (int i = 0; i < 2; ++i) {
			for (int j = 0; j < 2; ++j) {
				a[i][j] = val;
			}
		}
	}
	
	//判断这个面的颜色是不是都是val
	int ok(int val)
	{
		for (int i = 0; i < 2; ++i) {
			for (int j = 0; j < 2; ++j) {
				if (a[i][j] != val) {
					return 0;
				}
			}
		}
		return 1;
	}
}face[N];

//逆时针标出旋转的四个面
int U[] = {4, 1, 5, 0};
int F[] = {4, 2, 5, 3};
int R[] = {0, 2, 1, 3};
char str[N];

//判断是否还原
int check(void)
{
	for (int i = 0; i < 6; ++i) {
		if (!face[i].ok(i)) {
			return 0;
		}
	}
	return 1;
}

void up(void)
{
	int tmp[2];
	tmp[0] = face[U[3]].a[0][0];
	tmp[1] = face[U[3]].a[0][1];
	for (int i = 2; i >= 0; --i) {
		for (int j = 0; j < 2; ++j) {
			face[U[i + 1]].a[0][j] = face[U[i]].a[0][j];
		}
	}
	face[U[0]].a[0][0] = tmp[0];
	face[U[0]].a[0][1] = tmp[1];
	Face t = face[2];
	face[2].a[0][0] = t.a[1][0];
	face[2].a[0][1] = t.a[0][0];
	face[2].a[1][0] = t.a[1][1];
	face[2].a[1][1] = t.a[0][1];
}

void front(void)
{
	int tmp[2];
	tmp[0] = face[F[3]].a[1][0];
	tmp[1] = face[F[3]].a[1][1];
	face[F[3]].a[1][0] = face[F[2]].a[1][0];
	face[F[3]].a[1][1] = face[F[2]].a[0][0];
	face[F[2]].a[1][0] = face[F[1]].a[1][1];
	face[F[2]].a[0][0] = face[F[1]].a[1][0];
	face[F[1]].a[1][1] = face[F[0]].a[0][1];
	face[F[1]].a[1][0] = face[F[0]].a[1][1];
	face[F[0]].a[0][1] = tmp[0];
	face[F[0]].a[1][1] = tmp[1];
	Face t = face[0];
	face[0].a[0][0] = t.a[1][0];
	face[0].a[0][1] = t.a[0][0];
	face[0].a[1][0] = t.a[1][1];
	face[0].a[1][1] = t.a[0][1];
}

void right(void)
{
	int tmp[2];
	tmp[0] = face[R[3]].a[0][1];
	tmp[1] = face[R[3]].a[1][1];
	face[R[3]].a[0][1] = face[R[2]].a[0][0];
	face[R[3]].a[1][1] = face[R[2]].a[1][0];
	face[R[2]].a[0][0] = face[R[1]].a[1][1];
	face[R[2]].a[1][0] = face[R[1]].a[0][1];
	face[R[1]].a[1][1] = face[R[0]].a[1][1];
	face[R[1]].a[0][1] = face[R[0]].a[0][1];
	face[R[0]].a[1][1] = tmp[0];
	face[R[0]].a[0][1] = tmp[1];
	Face t = face[5];
	face[5].a[0][0] = t.a[1][0];
	face[5].a[0][1] = t.a[0][0];
	face[5].a[1][0] = t.a[1][1];
	face[5].a[1][1] = t.a[0][1];
}

int main(int argc, char *argv[])
{
	int len;
	while (scanf("%d%s", &len, str) != EOF) {
		//0-5分别代表前后上下左右 
		for (int i = 0; i < 6; ++i) {
			face[i].set(i);
		}
		int ans = 0;
		while (1) {
			for (int i = 0; i < len; ++i) {
				switch (str[i]) {
					case 'U' : up(); break;
					case 'F' : front(); break;
					case 'R' : right(); break;
				}
			}
			++ans;
			if (check()) {
				break;
			}
		}
		printf("%d\n", ans);
	}
	
	return 0;
} 

直接判断给定的字符串中有多少个洞就行了,A,D,O,P,Q,R有1一个洞,B有两个洞。
代码:
#include <iostream>
#include <string>

using namespace std;

int val[] = {1, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
		0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};

int main(int argc, char *argv[])
{
	int t;
	cin >> t;
	while (t--) {
		string s;
		cin >> s;
		int len = s.length();
		int ans = 0;
		for (int i = 0; i < len; ++i) {
			int idx = s[i] - 'A';
			ans += val[idx];
		}
		cout << ans << endl;
	}
	
	return 0;
}

基本上算一到数学题,没什么难度,因为给定的k太大了(10^10000),这个k用64位整数都存不下,只能用字符串来存。
斐波那契数定义为,F(n)=F(n-1)+F(n-2),(n>=2) ,其中F(0)=0,F(1)=1。 
我们根据奇数+奇数=偶数,奇数+偶数=奇数,偶数+偶数=偶数,很容易就推出当且仅当k为3的倍数时,F[k]为偶数。
判断一个数是不是3的倍数,小学学过的方法是把所有位上的数字加起来看看这个和能不能被3整除;另一个方法是边加边求余数,最后看看这个余数是不是0。
代码如下:
#include <iostream>
#include <cstring>

using namespace std;

char mp[][10] = {"YES", "NO", "NO"};

int main(int argc, char *argv[])
{
	int t;
	cin >> t;
	while (t--) {
		string s;
		cin >> s;
		int len = s.length();
		int ans = 0;
		for (int i = 0; i < len; ++i) {
			ans = (ans * 10 + s[i] - '0') % 3;
		}
		cout << mp[ans] << endl;
	}
	
	return 0;
}

判断是否存在这样的两个数a,b,他们的最大公约数和最小公倍数是分别是m,n。给定了m,n,求a,b,如果存在多组解,就输出a值最小的。
m是a,b的最大公约数,我们设a=cm,b=dm,因为a*b=m*n,所以有a*b=c*d*m*m=m*n,就得到了n=c*d*m,由此可知,n必定是m的倍数,而n为m的倍数时也必定存在解,因为要求a最小,所以可取c=1,那么就得到了a=m,b=n,也就是说,直接判断n是不是m的倍数,如果是,直接输出这两个数,否则输出给定的字符串。
但要注意题目给定的输入要求。
代码:
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
	int t;
	while (cin >> t) {
		while (t--) {
			long long a, b;
			cin >> a >> b;
			if (b % a == 0) {
				cout << a << " " << b << endl;
			} else {
				cout << "senior Tang is so smart!" << endl;
			}
		}
	}
	
	return 0;
}

这个题就是一个最长公共子序列(LCS),不过数据量太大,不能用普通的O(n^2)的算法,要寻求一种更高效的算法。
下面这个算法将LCS转化成最长上升子序列(LIS),LIS有一个O(nlogn)的算法,可以解决这个问题。
对于两个序列A[1...n]和B[1...m],我们先假定A中的元素在B中不会重复出现,即要么没有,要么只有一个。我们先找出A中元素在B中的位置(如果没有可假定位置为无穷大或者干脆不记录它),依次将其排列下来,然后对这个序列求LIS,得到的长度就是这两个序列的LCS长度。如果A中的元素在B中可能重复出现,就找出所有的位置,将这些位置降序排列,然后再将其依次写下来,其余就是求LIS了。
事实上,这道题目是白书上的原题,在66页。证明就不证了,自己悟吧。
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

#define N 80005

int num1[N], num2[N], idx[N], p[N], c[N], cnt, len;

int get(int pos)
{
	int l = 1;
	int r = len - 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (c[mid] < idx[pos] && c[mid + 1] >= idx[pos]) {
			return mid + 1;
		} else if (c[mid] < idx[pos]) {
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}

	//找不到就更新长度为1的,
	//否则就到不了这一步了
	return 1;
}

int lis(void)
{
	len = 0;
	c[0] = -1;
	for (int i = 0; i < cnt; ++i) {
		if (idx[i] > c[len]) {
			c[++len] = idx[i];
		} else {
			int pos = get(i);
			c[pos] = idx[i];
		}
	}
	return len;
}

/*
 * 将最长公共子序列(LCS)转换成最长上升子序列(LIS)
 * 找出依次第一个序列中的元素在第二个序列中的位置
 * (如果有多个位置,就将位置降序,降序的原因是A中
 * 这个位置的元素只能选择一次),
 * 排成一个序列,对这个位置序列求LIS,LIS的长度
 * 就是两个串LCS的长度。
 */
int main(int argc, char *argv[])
{
	int n, x, y;
	int k = 0;
	while (cin >> n >> x >> y) {
		memset(p, -1, sizeof(p));
		for (int i = 0; i < x; ++i) {
			cin >> num1[i];
		}
		for (int i = 0; i < y; ++i) {
			cin >> num2[i];
		}
		for (int i = 0; i < y; ++i) {
			p[num2[i]] = i;
		}
		cnt = 0;
		for (int i = 0; i < x; ++i) {
			if (p[num1[i]] >= 0) {
				idx[cnt++] = p[num1[i]];
			}
		}
		++k;
		cout << "Case " << k << ": " << lis() << endl;
	}

	return 0;
}

这道题就是一个裸的判断点是否在一个凸多边形内,方法有很多,最简单的是用叉积,或者说一个三角形的有向面积来计算。
假如一个点在一个m条边的凸多边形内或边上,那么这个点就依次可以和这m个顶点组成m个三角形(可能有的三角形的三点共线,但这不影响结论),分别求出三角形的面积(不是有向面积),将这m个面积加起来,就是这个凸多边形的面积。如果这个点不在凸多边形内部或边上,那么将这m个面积加加起来就会大于实际的面积。但将有向面积加起来就是实际面积(有向面积可以抵消),同时,这个求面积的方式适合凹多边形。那么我们就可以根据这个来判定这个点是不是在凸多边形内部或边上。这个面积判断法不适合凹多边形。
代码:
#include <cstdio>
#include <algorithm>

using namespace std;

#define N 110

struct Point {
	int x, y;
	Point(void)
	{
		this->x = 0;
		this->y = 0;
	}
	Point(int a, int b)
	{
		this->x = a;
		this->y = b;
	}
	
	int input(void)
	{
		return scanf("%d%d", &x, &y);
	}
}pt[N];

int getarea(Point A, Point B, Point C)
{
	int tmp1 = A.x * B.y + C.x * A.y + B.x * C.y;
	int tmp2 = C.x * B.y + A.x * C.y + B.x * A.y;
	
	return abs(tmp1 - tmp2);
}

//如果叉积为正,说明OAB三点以逆时针排列
//如果为负,说明OAB三点以顺时针排列
//如果为0,说明OAB三点共线
//同时叉积的意义为OAB三点组成的三角形的有向面积的二倍 
//因为这里所有的点都是整数,直接计算面积的二倍就行了
//不用使用浮点数,面积的二倍永远是整数 
//和上边getarea是一样的,不过上边是展开了 
int cross(Point O, Point A, Point B)
{
	int x1 = A.x - O.x;
	int y1 = A.y - O.y;
	int x2 = B.x - O.x;
	int y2 = B.y - O.y;
	
	return abs(x1 * y2 - x2 * y1);
}

int main(void)
{
	int n, m;
	Point s;
	while (s.input() != EOF) {
		scanf("%d%d", &n, &m);
		int ans = 0;
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < m; ++j) {
				pt[j].input();
			}
			int area1 = 0;
			int area2 = 0;
			pt[m] = pt[0];
			for (int j = 0; j < m; ++j) {
//				area1 += getarea(s, pt[j], pt[j + 1]);
				area1 += cross(s, pt[j], pt[j + 1]); 
			}
			for (int j = 0; j < m; ++j) {
//				area2 += getarea(pt[0], pt[j], pt[j + 1]);
				area2 += cross(pt[0], pt[j], pt[j + 1]);
			}
			if (area1 == area2) {
				ans = i + 1;
			}
		}
		printf("%d\n", ans);
	}
	
	return 0;
}

这道题要使路径所有结点间距离的最大值最小,首先考虑多重边,如果A到B有多条边,我们可以舍弃长边,选长边可能会使最大值变大,而选短边要么不变,要么最大值变小,因此选短边比选长边优。其次,所有结点的路径一定是一颗最小生成树上的路径;如果不是一棵树,我们可以生成一颗最小生成树,这就保证所有结点是连通的,也保证了路径最大值最小,如果这棵树不是最小生成树,那么我们可以将其替换成最小生成树,这样某些边会变得更优。用最小生成树的算法来写,克鲁斯卡尔算法比较容易实现。合并边的时候,看看两边分别有多少个结点,相乘,就是以当前这个边为最长距离的路径个数,所以并查集合并的时候要记录以n为根结点的结点个数,同时记录以这条边为最长距离的路径个数。同时这个最长距离也是最小的。最后计算一下最长距离最小值中的最大值和最小值得个数就是答案。这里要注意因为A->B和B->A是不同的,所以最后记录的个数要乘以2。
代码如下:
#include <cmath>
#include <ctime>
#include <cctype>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <map>
#include <set>
#include <queue>
#include <stack>
#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <algorithm>

#define INF (INT_MAX / 10)
#define clr(arr, val) memset(arr, val, sizeof(arr))
#define pb push_back
#define sz(a) ((int)(a).size())

using namespace std;
typedef set<int> si;
typedef vector<int> vi;
typedef map<int, int> mii;
typedef long long ll;

const double esp = 1e-5;

#define N 1010

struct Node {
	int u, v, d;
	void input(void)
	{
		scanf("%d%d%d", &u, &v, &d);
	}
}node[N];

int n, m, c[N], d[N], ans[N], num[N];

bool cmp(Node a, Node b)
{
	return a.d < b.d;
}

int find(int n)
{
	if (c[n] == n) {
		return n;
	} else {
		return c[n] = find(c[n]);
	}
}

int main(int argc, char *argv[])
{
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &m);
		for (int i = 0; i < m; ++i) {
			node[i].input();
		}
		sort(node, node + m, cmp);
		for (int i = 1; i <= n; ++i) {
			c[i] = i;
			num[i] = 1;
		}
		int cnt = 0;
		for (int i = 0; i < m; ++i) {
			int t1 = find(node[i].u);
			int t2 = find(node[i].v);
			int sum = num[t1] + num[t2];
			if (t1 != t2) {
				c[t1] = t2;
				d[cnt] = node[i].d;
				ans[cnt] = num[t1] * num[t2];
				num[t1] = sum;
				num[t2] = sum;
				++cnt;
			}
		}

		int mn = d[0];
		int mx = d[cnt - 1];
		int mnc = 0;
		int mxc = 0;
		for (int i = 0; i < cnt; ++i) {
			if (d[i] == mn) {
				mnc += ans[i];
			} else {
				break;
			}
		}
		for (int i = cnt - 1; i >= 0; --i) {
			if (d[i] == mx) {
				mxc += ans[i];
			} else {
				break;
			}
		}
		printf("%d %d\n", 2 * mnc, 2 * mxc);
	}

	return 0;
}

求1到n的偶数和,代码:
#include <cstdio>
#include <cstring>
 
using namespace std;
 
#define N 110
 
int dp[N];
 
int main(void)
{
    memset(dp, 0, sizeof(dp));
    dp[1] = 0;
    for (int i = 2; i <= 100; ++i) {
        dp[i] = dp[i - 1];
        if (i % 2 == 0) {
            dp[i] += i;
        }
    }
    int n;
    while (scanf("%d", &n) != EOF) {
        printf("%d\n", dp[n]);
    }
     
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值