CCPC 2020 秦皇岛 赛后补题

本文精选了几道算法竞赛题目,包括组合数签到题、寻找最长可见边、选择最佳考试成绩等,通过具体实现展示了多种算法思想和技术细节。

感觉这场题挺对胃口的,早知道就参加秦皇岛站比赛了(逃


A、Greeting from Qinhuangdao

组合数签到

#include <bits/stdc++.h>
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
int gcd(int a, int b)
{
	return b == 0 ? a : gcd(b, a % b);
}
int main()
{
	int T,cas = 1;
	sc("%d", &T);
	while (T--)
	{
		int a, b;
		sc("%d%d", &a, &b);
		b = a + b;
		a = a * (a - 1) / 2;
		b = b * (b - 1) / 2;
		int g = gcd(a, b);
		a /= g;
		b /= g;
		pr("Case #%d: %d/%d\n", cas++, a, b);
	}
}

C、Cameraman

Alex在一个矩形中,矩形中有n个物品,选择一个角度,Alex看不到物品并且看到的矩形边长最长,输出最长边长

将Alex所在的位置与每个物品的位置连线,成为一条射线,并且这条射线在矩形内,所以这条射线和矩形只有一个交点,考虑将所有交点按照顺时针或者逆时针顺序排在矩形上,计算两两之间的距离的最大值即为答案,考虑到算出点之后不好排序,我们直接对射线的倾斜角进行逆时针排序即可,然后枚举两个点距离的所有情况即可

#include <bits/stdc++.h>
#define sc scanf
#define pr printf
using namespace std;
const double eps = 1e-8;
const double PI = acos(-1.0);
int sign(double x) {
	if (fabs(x) < eps)return 0;
	if (x < 0)return -1;
	else return 1;
}
struct Point {
	double x, y;
	void input() {
		scanf("%lf%lf", &x, &y);
	}
	Point operator -(const Point& b)const {
		return Point{ x - b.x, y - b.y };
	}
	double operator ^(const Point& b)const {
		return x * b.y - y * b.x;
	}
	double operator *(const Point& b)const {
		return x * b.x + y * b.y;
	}
	double distance(Point p) {
		return hypot(x - p.x, y - p.y);
	}
};
struct Line {
	Point s, e;
	int linecrossseg(Line v) {
		int d1 = sign((e - s) ^ (v.s - s));
		int d2 = sign((e - s) ^ (v.e - s));
		if ((d1 ^ d2) == -2) return 2;
		return (d1 == 0 || d2 == 0);
	}
	Point crosspoint(Line v) {
		double a1 = (v.e - v.s) ^ (s - v.s);
		double a2 = (v.e - v.s) ^ (e - v.s);
		return Point{ (s.x * a2 - e.x * a1) / (a2 - a1), (s.y * a2 - e.y * a1) / (a2 - a1) };
	}
};
int n, m;
Line l[4];
Point p[100005];
Point s;
Point pp[100005];
Point get(Point p)
{
	Line op = Line{ p,s };
	for (int i = 0; i < 4; i++)
	{
		if (op.linecrossseg(l[i]) != 0)
		{
			Point pos = op.crosspoint(l[i]);
			if (pos.distance(p) < pos.distance(s))
				return pos;
		}
	}
	assert(1 == 0);
	cout << "Error";
	return Point{ -1,-1 };
}
double calc(Point a, Point b)
{
	if (a.x == n && a.y >= s.y)
	{
		if (b.x == n && b.y >= a.y)
			return b.y - a.y;
		else if (b.y == m)
			return m - a.y + n - b.x;
		else if (b.x == 0)
			return m - a.y + n + m - b.y;
		else if (b.y == 0)
			return m - a.y + n + m + b.x;
		else
			return m - a.y + n + m + n + b.y;
	}
	else if (a.y == m)
	{
		if (b.y == m)
			return a.x - b.x;
		else if (b.x == 0)
			return a.x + m - b.y;
		else if (b.y == 0)
			return a.x + m + b.x;
		else
			return a.x + m + n + b.y;
	}
	else if (a.x == 0)
	{
		if (b.x == 0)
			return a.y - b.y;
		else if (b.y == 0)
			return a.y + b.x;
		else
			return a.y + n + b.y;
	}
	else if (a.y == 0)
	{
		if (b.y == 0)
			return b.x - a.x;
		else
			return n - a.x + b.y;
	}
	else
	{
		return b.y - a.y;
	}
}
int main()
{
	//cout << atan2(1, -1) << " " << atan2(1, 0) << " " << atan2(1, 1) << endl;
	//cout << atan2(0, -1) << " " << atan2(0, 0) << " " << atan2(0, 1) << endl;
	//cout << atan2(-1, -1) << " " << atan2(-1, 0) << " " << atan2(-1, 1) << endl;
	int T, cas = 1;
	sc("%d", &T);
	while (T--)
	{
		sc("%d%d", &n, &m);
		s.input();
		int cnt;
		sc("%d", &cnt);
		l[0] = Line{ Point{0,0},Point{n,0} };
		l[1] = Line{ Point{0,0},Point{0,m} };
		l[2] = Line{ Point{n,m},Point{n,0} };
		l[3] = Line{ Point{n,m},Point{0,m} };
		for (int i = 0; i < cnt; i++)
			p[i].input();
		sort(p, p + cnt, [](Point q, Point w) {
			double qq = atan2(q.y - s.y, q.x - s.x);
			double ww = atan2(w.y - s.y, w.x - s.x);
			if (sign(qq) >= 0)
			{
				if (sign(ww) >= 0)
					return qq < ww;
				else
					return true;
			}
			else
			{
				if (sign(ww) >= 0)
					return false;
				else
					return qq < ww;
			}
			//return (q.y - s.y) * (w.x - s.x) < (w.y - s.y) * (q.x - s.x);
			});
		for (int i = 0; i < cnt; i++)
			pp[i] = get(p[i]);
		double ans = 0;
		for (int i = 1; i < cnt; i++)
		{
			double temp = calc(pp[i - 1], pp[i]);
			ans = max(ans, temp);
		}
		ans = max(ans, 2 * (n + m) - calc(pp[0], pp[cnt - 1]));
		pr("Case #%d: %.8lf\n", cas++, ans);
	}
}
/*
1
6 6
3 3
8
1 2
2 1
4 1
5 2
5 4
4 5
1 4
2 5
*/

E、Exam Results

每个人有两个成绩,你给每个人选择一个成绩,定义及格的人为分数大于最高分*p%,求及格的人的最大值

滑动窗口经典问题,需要最大值和最小值满足一些条件,注意这个问题每个人都需要一个分数,所以我们在做滑动窗口之前先保证每个人都有一个分数在滑动窗口里面即可

#include <bits/stdc++.h>
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
struct node
{
	int id;
	ll num;
}que[400005];
int book[200005];
int main()
{
	int T, cas = 1;
	sc("%d", &T);
	while (T--)
	{
		int n, p;
		sc("%d%d", &n, &p);
		int tot = 0;
		for (int i = 1; i <= n; i++)
		{
			book[i] = 0;
			ll a, b;
			sc("%lld%lld", &a, &b);
			que[++tot] = node{ i,a };
			que[++tot] = node{ i,b };
		}
		sort(que + 1, que + 1 + tot, [](node q, node w) {
			return q.num < w.num;
			});
		int head = 1, tail = 0;
		int ans = 1, now = 0;
		while (now != n)
		{
			tail++;
			if (book[que[tail].id] == 0)
				now++;
			book[que[tail].id]++;
		}
		if (book[que[tail].id] == 1)
			now--;
		book[que[tail].id]--;
		tail--;
		while (tail < 2 * n)
		{
			tail++;
			if (book[que[tail].id] == 0)
				now++;
			book[que[tail].id]++;
			while (que[head].num * 100 < que[tail].num * p)
			{
				if (book[que[head].id] == 1)
					now--;
				book[que[head].id]--;
				head++;
			}
			ans = max(ans, now);
		}
		pr("Case #%d: %d\n", cas++, ans);
	}
}
/*
1
5 10
1 1
1 1
1 1
1 1
100 100
*/

F、Friendly Group

solve by yp

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 1e6+5;
int t,n,m,f[MAX],du[MAX],Case=0,countt[MAX];
ll ans[MAX],answer;
int find(int v){
	return f[v]==v?f[v]:f[v]=find(f[v]);
}
int main(void)
{
	scanf("%d",&t);
	while(t--){
		++Case;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i){
			f[i]=i,du[i]=0;
			ans[i]=0;
			countt[i]=0;
		}
		for(int i=1;i<=m;++i){
			int a,b;
			scanf("%d%d",&a,&b);
			++du[a],++du[b];
			a=find(a),b=find(b);
			f[b]=a;
		}
		for(int i=1;i<=n;++i){
			int fa = find(i);
			ans[fa]+=du[i];
			++countt[fa];
		}
		answer=0;
		for(int i=1;i<=n;++i){
			ll now = ans[i]/2-countt[i];
			if(now>0) answer+=now;
//			answer=max(answer,ans[i]/2-countt[i]);
		}
		printf("Case #%d: %lld\n",Case,answer);
	}
	return 0;
}

G、Good Number

求1-n里面有多少个数字x满足 \sqrt[k]{x} | x

枚举开方后的数字,算范围内有多少是这个数字的倍数,签到

#include <bits/stdc++.h>
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
int gcd(int a, int b)
{
	return b == 0 ? a : gcd(b, a % b);
}
ll n, k;
ll power(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)
			res = res * a;
		if (res > n || a > n)
			return -1;
		a = a * a;
		b >>= 1;
	}
	return res;
}
ll calc(ll k, ll pre, ll nex)
{
	pre = (pre / k) * k + (pre % k == 0 ? 0 : k);
	nex = (nex / k) * k;
	ll ans = (nex - pre) / k + 1;
	return ans;
}
int main()
{
	int T, cas = 1;
	sc("%d", &T);
	while (T--)
	{
		sc("%lld%lld", &n, &k);
		if (k == 1)
		{
			pr("Case #%d: %lld\n", cas++, n);
			continue;
		}
		ll ans = 0;
		for (ll x = 1; x <= 100000; x++)
		{
			ll pre = power(x, k);
			ll nex = power(x + 1, k);
			if (pre == -1)
				break;
			if (nex == -1)
				nex = n + 1;
			ans += calc(x, pre, nex - 1);
		}
		pr("Case #%d: %lld\n", cas++, ans);
	}
}

K、Kingdom's Power

给一棵树,边权是1,在1号位置很多个军队,你每秒只能让一个军队沿着树移动一个距离,求走遍全图的最小时间

对于每一个分叉点,本质上就是判断是从深度更小的点回到分叉点更快还是从1号点调新的军队来更快,所以我们考虑优先走最大深度最小的点,在考虑是否需要调新的军队的时候,只需要考虑这个分叉点上一次遍历的位置调过来是否更快即可(感性理解一下),所以记录每个分叉点的军队上一次访问到的位置进行判断即可

#include <bits/stdc++.h>
#define ll long long
#define sc scanf
#define pr printf
using namespace std;
const int MAXN = 1e6 + 5;
vector<int>v[MAXN];
int deep[MAXN], deepmax[MAXN];
void dfs1(int u, int fa)
{
	deep[u] = deep[fa] + 1;
	deepmax[u] = deep[u];
	int len = v[u].size();
	for (int i = 0; i < len; i++)
	{
		int to = v[u][i];
		if (to == fa)
			continue;
		dfs1(to, u);
		deepmax[u] = max(deepmax[u], deepmax[to]);
	}
}
int pre[MAXN];
ll ans;
void dfs2(int u, int fa)
{
	int len = v[u].size();
	pre[u] = u;
	for (int i = 0; i < len; i++)
	{
		int to = v[u][i];
		if (to == fa)
			continue;
		ans += min(deep[u] - 1, deep[pre[u]] - deep[u]);//从1拿军队还是从pre拿军队
		ans++;//u->to
		dfs2(to, u);
		pre[u] = pre[to];
	}
}
int main()
{
	int T, cas = 1;
	sc("%d", &T);
	while (T--)
	{
		ans = 0;
		int n;
		sc("%d", &n);
		for (int i = 1; i <= n; i++)
		{
			v[i].clear();
			pre[i] = 0;
		}
		for (int i = 2; i <= n; i++)
		{
			int t;
			sc("%d", &t);
			v[i].push_back(t);
			v[t].push_back(i);
		}
		dfs1(1, 0);
		for (int i = 1; i <= n; i++)
			sort(v[i].begin(), v[i].end(), [](int q, int w) {
				return deepmax[q] < deepmax[w];
				});
		dfs2(1, 0);
		pr("Case #%d: %lld\n", cas++, ans);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值