北邮ACM新生赛2015 题解

比赛地址

没权限找不到=_=。。。题目为http://code.bupt.edu.cn/的616, 614, 605, 604, 440, 620, 621, 619, 617。


A. Asce的树

题意:

有一个半径为r的小圆,现在尝试用半径为R的大圆来完全包围住这个圆,问至少需要几个圆。

r, R <= 10^5。

题解:

考虑相邻的两个半径为R的圆,最好的情况是均与半径为r的圆相切,将三圆圆心连线得到相邻的两个半径为R的圆所覆盖的圆心角(即一个圆能覆盖的最大圆心角),算出至少要用多少个圆来覆盖360°。

代码:

#include <cmath>
#include <cstdio>
const double pi = acos(-1);
double a, b;
int main()
{
	while(scanf("%lf%lf", &a, &b) == 2)
		printf("%d\n", (int)ceil(pi / asin(b / (a + b))));
	return 0;
}

B. cjj的树

题意:

按照时针顺序给出n多边形的顶点,按照两点式给出一条直线,问直线在n多边形内部的线段长度之和。

n <= 100000, |坐标| <= 10000,坐标为整数。

题解:

考虑直线与n多边形每条边的交点,将其排序后得到直线被n多边形切割的不超过n个切割点,依次相连的线段可能在n多边形内。

如果不考虑直线经过多边形的顶点,那么按顺序考虑每个交点,相邻交点之间的线段一定是在多边形内与不在多边形内这两种情况的交替。

考虑经过多边形的顶点,那么这个顶点会被当作两个交点,如果直线在这两条边的中间,那么线段的情况会发生变化,否则这两条边在直线的同侧,线段的情况不会发生变化,只需要判断这两条边的另外两个点是否在直线的同侧即可。

时间复杂度O(nlogn)。

代码:

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100001;
const double eps = 1e-8;
inline int sgn(double x)
{
	return (x > eps) - (x < -eps);
}
struct Point
{
	int id;
	double x, y;
	bool operator == (const Point &t) const
	{
		return !sgn(x - t.x) && !sgn(y - t.y);
	}
	bool operator < (const Point &t) const
	{
		return sgn(x - t.x) < 0 || !sgn(x - t.x) && sgn(y - t.y) < 0;
	}
	Point operator - (const Point &t) const
	{
		return (Point){id, x - t.x, y - t.y};
	}
	Point operator + (const Point &t) const
	{
		return (Point){id, x + t.x, y + t.y};
	}
	Point operator * (const double &t) const
	{
		return (Point){id, x * t, y * t};
	}
	double dot(const Point &t) const
	{
		return x * t.x + y * t.y;
	}
	double det(const Point &t) const
	{
		return x * t.y - y * t.x;
	}
	double mode() const
	{
		return sqrt(dot(*this));
	}	
} p[maxn], A, B, pp[maxn];
int n, tot;
double ans;
inline bool isIns(Point aL, Point aR, Point bL, Point bR) // Line aL aR  Segment bL bR
{
	return sgn((aR - aL).det(bR - aL)) * sgn((aR - aL).det(bL - aL)) <= 0;
}
inline Point SegIns(Point aL, Point aR, Point bL, Point bR)
{
	Point u = aL - bL, v = aR - aL, w = bR - bL;
	return aL + (aR - aL) * ((bR - bL).det(aL - bL) / (aR - aL).det(bR - bL));
}
int main()
{
	while(scanf("%d", &n) == 1)
	{
		for(int i = 0; i < n; ++i)
			scanf("%lf%lf", &p[i].x, &p[i].y);
		scanf("%lf%lf%lf%lf", &A.x, &A.y, &B.x, &B.y);
		tot = 0;
		for(int i = 0; i < n; ++i)
			if(isIns(A, B, p[i], p[(i + 1) % n]))
			{
				pp[tot] = SegIns(A, B, p[i], p[(i + 1) % n]);
				pp[tot++].id = i;
			}
		sort(pp, pp + tot);
		ans = 0;
		for(int i = 0, flag = 0; i < tot; ++i)
		{
			if(flag)
				ans += (pp[i - 1] - pp[i]).mode();
			if(i + 1 < n && pp[i] == pp[i + 1])
			{
				Point a = pp[i] == p[pp[i].id] ? p[(pp[i].id + 1) % n] : p[pp[i].id];
				Point b = pp[i + 1] == p[pp[i + 1].id] ? p[(pp[i + 1].id + 1) % n] : p[pp[i + 1].id];
				if(isIns(A, B, a, b))
					flag ^= 1;
				++i;
			}
			else
				flag ^= 1;
		}
		printf("%.4f\n", ans);
	}
	return 0;
}

C. 陈队的树

题意:

给定n个数{a_n},再给出m个数{b_m}。对于一个b_i,可以选择一个超过b_i的a_j使其修改为b_i,也可以不选择。每个数只能被修改一次,问经过修改后这n个数之和的最小值。

n, m <= 1000, 数字 <= 1000。

题解:

贪心,用小的b_i去修改大的a_j不会使解变差,将{a_n}和{b_m}分别排序,扫一遍即可。

不过有一个显然的结论,最终这n个数会是这n+m个数里最小的n个。

时间复杂度O(nlogn)。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 2001;
int t, n, m, a[maxn], ans;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		ans = 0;
		scanf("%d%d", &n, &m);
		for(int i = 0, x; i < n + m; ++i)
			scanf("%d", a + i);
		sort(a, a + n + m);
		for(int i = 0; i < n; ++i)
			ans += a[i];
		printf("%d\n", ans);
	}
	return 0;
}

D. 圣哲的树

题意:

有12个砝码,编号A~L,一个是坏的,现在给出3次称量的结果,问是否能唯一确定坏的砝码是哪个,是轻或是重。

称量保证天平两边砝码数量相同,用天平右侧向上偏、向下偏、不动表示结果。

题解:

由于天平两边砝码数量是相同的,所以可以确定任意质量的坏砝码。

枚举坏的砝码是哪一个,带入三次结果,尝试算出是轻还是重,如果有唯一确定解则输出。

代码:

#include <cstdio>
int t, flag, ans;
char state[3][3][20];
bool check(int x, int msk)
{
	for(int i = 0; i < 3; ++i)
	{
		int cL = 0, cR = 0;
		for(int j = 0; state[i][0][j]; ++j)
			if(state[i][0][j] == 'A' + x)
				cL += msk ? 1 : -1;
		for(int j = 0; state[i][1][j]; ++j)
			if(state[i][1][j] == 'A' + x)
				cR += msk ? 1 : -1;
		if(state[i][2][0] == 'u' && cL <= cR
		|| state[i][2][0] == 'e' && cL != cR
		|| state[i][2][0] == 'd' && cL >= cR)
			return 0;
	}
	return 1;
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		for(int i = 0; i < 3; ++i)
			for(int j = 0; j < 3; ++j)
				scanf("%s", state[i][j]);
		flag = 0;
		for(int i = 0; i < 12; ++i)
			for(int j = 0; j < 2; ++j)
				if(check(i, j))
				{
					++flag;
					ans = i << 1 | j;
				}
		if(flag != 1)
			puts("cannot judge.");
		else
			printf("%c is %s.\n", 'A' + (ans >> 1), ans & 1 ? "heavy" : "light");
	}
	return 0;
}

E. 彭学长的树

题意:

现在有一个斗地主游戏,给定牌面大小的比较,再给出上家的出牌和下家的手牌,问下家是否能出牌大过上家。

每人牌数 <= 20。

题解:

根据规则检查上家的牌是什么,再看下家是否能相应地给出更大的牌,模拟即可,注意三带一和飞机带翅膀这两种情况的判断。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 15;
const char *pai = "3456789TJQKA2XY";
int trans[256];
char yours[25], mine[25];
int yid, yB, yK1, yC, yK2, yS[maxn];
int msz[5], mcnt[maxn], mx[5], mS[maxn];
//			mind mx[4] include XY
void judge()
{
	int Ssz = 0, S[25] = {};
	int Csz = 0, C[maxn] = {};
	for(int i = 0; yours[i]; ++i)
		S[Ssz++] = trans[(int)yours[i]];
	sort(S, S + Ssz);
	for(int i = 0; i < Ssz; ++i)
	{
		if(!i || S[i - 1] != S[i])
			++Csz;
		++C[Csz - 1];
	}
	unique(S, S + Ssz); // Ssz = Csz;
	if(Ssz == 2 && Csz == 2 && S[0] == trans[(int)'X'] && S[1] == trans[(int)'Y']
	|| Ssz == 4 && Csz == 1)
	{
		yid = 0;
		yB = S[0];
		return;
	}
	if(Ssz == 1)
	{
		yid = 1;
		yB = S[0];
		return;
	}
	if(Ssz == 2 && Csz == 1)
	{
		yid = 2;
		yB = S[0];
		return;
	}
	if(Ssz == 3 && Csz ==1)
	{
		yid = 3;
		yB = S[0];
		return;
	}
	if((Ssz == 4 || Ssz == 5) && Csz == 2)
	{
		if(C[1] == 3)
		{
			swap(S[0], S[1]);
			swap(C[0], C[1]);
		}
		yid = 4;
		yB = S[0];
		yC = S[1];
		yK2 = C[1];
		return;
	}
	if(Ssz == Csz && Csz == S[Csz - 1] - S[0] + 1)
	{
		yid = 5;
		yB = S[0];
		yK1 = Csz;
		return;
	}
	if(Ssz == Csz * 2 && Csz == S[Csz - 1] - S[0] + 1)
	{
		bool flag = 1;
		for(int i = 0; i < Csz && flag; ++i)
			flag &= C[i] == 2;
		if(flag)
		{
			yid = 6;
			yB = S[0];
			yK1 = Csz;
			return;
		}
	}
	if(Ssz == Csz * 3 && Csz == S[Csz - 1] - S[0] + 1)
	{
		bool flag = 1;
		for(int i = 0; i < Csz && flag; ++i)
			flag &= C[i] == 2;
		if(flag)
		{
			yid = 7;
			yB = S[0];
			yK1 = Csz;
			return;
		}
	}
	if(Ssz % 4 == 0 && Csz * 4 == Ssz * 2)
	{
		int tmp1 = 0, tmp2 = 0, Max = -1, Min = 233;
		for(int i = 0; i < Csz; ++i)
			if(C[i] == 3)
			{
				++tmp1;
				if(S[i] > Max)
					Max = S[i];
				if(S[i] < Min)
					Min = S[i];
			}
			else if(C[i] == 1)
				yS[tmp2++] = S[i];
		if(2 * tmp1 == Csz && tmp1 == Max - Min + 1 && tmp1 == tmp2)
		{
			yid = 8;
			yB = Min;
			yK1 = tmp1;
			yK2 = 1;
			return;
		}
	}
	if(Ssz % 5 == 0 && Csz * 5 == Ssz * 2)
	{
		int tmp1 = 0, tmp2 = 0, Max = -1, Min = 233;
		for(int i = 0; i < Csz; ++i)
			if(C[i] == 3)
			{
				++tmp1;
				if(S[i] > Max)
					Max = S[i];
				if(S[i] < Min)
					Min = S[i];
			}
			else if(C[i] == 2)
				yS[tmp2++] = S[i];
		if(2 * tmp1 == Csz && tmp1 == Max - Min + 1 && tmp1 == tmp2)
		{
			yid = 8;
			yB = Min;
			yK1 = tmp1;
			yK2 = 2;
			return;
		}
	}
	if(Ssz > 4)
	{
		int tmp = 0;
		for(int i = 0; i < Csz; ++i)
			if(C[i] == 4)
				++tmp;
		if(tmp == 1)
		{
			yid = 9;
			return;
		}
	}
	yid = -1;
	puts("invalid");
}
void init()
{
	memset(msz, 0, sizeof msz);
	memset(mcnt, 0, sizeof mcnt);
	memset(mx, -1, sizeof mx);
	for(int i = 0; mine[i]; ++i)
	{
		if(trans[(int)mine[i]] == -1)
		{
			puts("invalid");
			continue;
		}
		++mcnt[trans[(int)mine[i]]];
	}
	for(int i = 0; i < maxn; ++i)
	{
		++msz[mcnt[i]];
		mx[mcnt[i]] = i;
	}
	for(int i = 3; i >= 1; --i)
	{
		msz[i] += msz[i + 1];
		if(mx[i] < mx[i + 1])
			mx[i] = mx[i + 1];
	}
	if(mcnt[trans[(int)'X']] && mcnt[trans[(int)'Y']])
		mx[4] = trans[(int)'X'];
}
bool check()
{
	if(yid == 0)
		return mx[4] > yB;
	if(mx[4] != -1)
		return 1;
	switch(yid)
	{
		case 1: return mx[1] > yB;
		case 2: return mx[2] > yB;
		case 3: return mx[3] > yB;
		case 4:
			if(mx[3] != yB || msz[yK2] == 1)
				return mx[3] > yB && msz[yK2] > 1;
			mcnt[mx[3]] = 0;
			for(int i = maxn - 1; i >= 0; --i)
				if(mcnt[i] >= yK2)
					return i > yC;
			return 0;
		case 5:
			for(int i = maxn - 4, j = 0; i >= 0; --i)
			{
				j = mcnt[i] >= 1 ? j + 1 : 0;
				if(j == yK1)
					return i > yB;
			}
			return 0;
		case 6:
			for(int i = maxn - 4, j = 0; i >= 0; --i)
			{
				j = mcnt[i] >= 2 ? j + 1 : 0;
				if(j == yK1)
					return i > yB;
			}
			return 0;
		case 7:
			for(int i = maxn - 4, j = 0; i >= 0; --i)
			{
				j = mcnt[i] >= 3 ? j + 1 : 0;
				if(j == yK1)
					return i > yB;
			}
			return 0;
		case 8:
			for(int i = maxn - 4, j = 0; i >= 0; --i)
			{
				j = mcnt[i] >= 3 ? j + 1 : 0;
				if(j == yK1)
				{
					if(i < yB)
						return 0;
					for(int t = 0; t < yK1; ++t)
						mcnt[i + t] = 0;
					int tmp = yK1;
					for(int t = maxn - 1; t >= 0; --t)
						if(tmp > 0 && mcnt[t] >= yK2)
							mS[--tmp] = t;
					if(tmp > 0)
						return 0;
					if(i > yB)
						return 1;
					for(int t = yK1 - 1; t >= 0; --t)
						if(yS[t] != mS[t])
							return yS[t] < mS[t];
					return 0;
				}
			}
		case 9: return 0;
	}
	return 0;
}
int main()
{
	memset(trans, -1, sizeof trans);
	for(int i = 0; i < maxn; ++i)
		trans[(int)pai[i]] = i;
	while(scanf("%s%s", yours, mine) == 2)
	{
		judge(); // judge yours
		init(); // init mine
		puts(check() ? "Yes" : "No"); // check Yes or No
	}
	return 0;
}

F. winoros的树

题意:

给出四个木棒,问是否能选出三个木棒组成三角形。

题解:

排个序然后枚举木棒,进行检查。

或者直接检查也可,只有4种可能。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
int t, a[4];
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		for(int i = 0; i < 4; ++i)
			scanf("%d", a + i);
		sort(a, a + 4);
		bool flag = 0;
		for(int i = 0; i < 4 && !flag; ++i)
			for(int j = i + 1; j < 4 && !flag; ++j)
				for(int k = j + 1; k < 4 && !flag; ++k)
					flag |= a[i] + a[j] > a[k];
		puts(flag ? "YES" : "NO");
	}
	return 0;
}

G. 廖神的树

题意:

有一个n*n的01矩阵,现在已知前m行的值,问剩下(n - m)行有多少种填法使得该矩阵的每一行每一列都恰好有2个1,保证输入数据前m行满足这个条件,问答案模mod的值。

n <= 500, 0 <= m <= n, mod <= 1e9。

题解:

考虑向每一行添加两个数字,实际上只有三种情况,向两个没有1的列添加1,向两个还差1的列添加1,向一个没有1的列和一个还差1的列添加1。

则可以用f[i][j]表示有i个列没有1,j个列还差1的情况下所求的方案数,直接排列组合即可,注意到这里并不需要依次填写每一行2个1的位置,所以不需要加上行号这一维。

时间复杂度O(n^2)。

代码:

#include <cstdio>
#include <cstring>
int n, m, mod, r[555], cnt[2], f[555][555];
char str[555];
int main()
{
	while(scanf("%d%d%d", &n, &m, &mod) == 3)
	{
		memset(r, 0, sizeof r);
		memset(cnt, 0, sizeof cnt);
		memset(f, 0, sizeof f);
		for(int i = 0; i < m; ++i)
		{
			scanf("%s", str);
			for(int j = 0; j < n; ++j)
				if(str[j] == '1')
					++r[j];
		}
		for(int i = 0; i < n; ++i)
			++cnt[r[i]];
		f[0][0] = 1;
		for(int i = 0; i <= cnt[0]; ++i)
			for(int j = 0; j <= n; ++j)
			{
				if(i >= 2 && j + 2 <= n)
				{
					f[i][j] += (long long)i * (i - 1) / 2 * f[i - 2][j + 2] % mod;
					if(f[i][j] >= mod)
						f[i][j] -= mod;
				}
				if(j >= 2)
				{
					f[i][j] += (long long)j * (j - 1) / 2 * f[i][j - 2] % mod;
					if(f[i][j] >= mod)
						f[i][j] -= mod;
				}
				if(i && j)
				{
					f[i][j] += (long long)i * j * f[i - 1][j] % mod;
					if(f[i][j] >= mod)
						f[i][j] -= mod;
				}
			}
		printf("%d\n", f[cnt[0]][cnt[1]]);
	}
	return 0;
}

H. wzt的树

题意:

给定n个数{h_n},和q个询问,每次询问给定一个数hh,问满足h_i <= hh的i的个数。

n <= 50000, q <= 50000。

题解:

将n个数排序后直接二分查找即可,可以使用upper_bound函数快速计算容器内小于等于定值的下标最大值。

时间复杂度O((n + q)logn)。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 50001;
int t, n, h[maxn], q, x;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d", &n);
		for(int i = 0; i < n; ++i)
			scanf("%d", h + i);
		sort(h, h + n);
		scanf("%d", &q);
		for(int i = 0; i < q; ++i)
		{
			scanf("%d", &x);
			printf("%d\n", lower_bound(h, h + n, x + 1) - h);
		}
	}
	return 0;
}

I. 焦叔的树

题意:

给定n和m,定义初始序列为{1, 2, ..., n},有m个操作,分为两种,一种是将原序列的第l位到第r位每个数字再写一遍放到这个数字后面,另一种是询问现在数列的第l位到第r位里出现频率最高的数字有多少个。

n, m <= 10000, 每次操作的区间长度 <= 10^8。

题解:

复制操作并不会改变序列单调的性质,可以考虑将相同的数字放在一起,统计出现次数。

则复制操作可以视作将某两个数字(可能不存在)的次数加上定值、将一段数字的次数均乘2,询问操作即询问某两个数值和一段数字的次数的最大值。

而查找这一段数字的区间可以利用二分的方法。

考虑用线段树维护"次数序列"一段区间的最大值和数值之和,并打上乘2的标记,再支持一个在树上走的操作,利用二分(分治)思想找到一段数字的区间的端点。

时间复杂度O(nlogn)。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 10001;
int t, n, m;
LL sz[maxn << 1], mx[maxn << 1], tag[maxn << 1];
inline void upd(LL &x, LL y)
{
	if(x < y)
		x = y;
}
inline int idx(int L, int R)
{
	return L + R | L != R;
}
void init(int L, int R)
{
	int o = idx(L, R);
	sz[o] = R - L + 1;
	mx[o] = tag[o] = 1;
	if(L == R)
		return;
	int M = L + R >> 1;
	init(L, M);
	init(M + 1, R);
}
void push_down(const int &o, const int &lch, const int &rch)
{
	if(tag[o] == 1)
		return;
	sz[lch] *= tag[o];
	mx[lch] *= tag[o];
	tag[lch] *= tag[o];
	sz[rch] *= tag[o];
	mx[rch] *= tag[o];
	tag[rch] *= tag[o];
	tag[o] = 1;
}
void push_up(const int &o, const int &lch, const int &rch)
{
	sz[o] = sz[lch] + sz[rch];
	mx[o] = max(mx[lch], mx[rch]);
}
pair<int, LL> find(int L, int R, LL res, bool flag) // 0 Left 1 Right
{
	int o = idx(L, R);
	if(L == R)
		return make_pair(L, flag ? res : sz[o] - res + 1);
	int M = L + R >> 1, lch = idx(L, M), rch = idx(M + 1, R);
	push_down(o, lch, rch);
	pair<int, LL> ret;
	if(res <= sz[lch])
		ret = find(L, M, res, flag);
	else
		ret = find(M + 1, R, res - sz[lch], flag);
	push_up(o, lch, rch);
	return ret;
}
void cha_x(int L, int R, int x, LL v)
{
	int o = idx(L, R);
	if(L == R)
	{
		sz[o] += v;
		mx[o] = sz[o];
		return;
	}
	int M = L + R >> 1, lch = idx(L, M), rch = idx(M + 1, R);
	push_down(o, lch, rch);
	if(x <= M)
		cha_x(L, M, x, v);
	else
		cha_x(M + 1, R, x, v);
	push_up(o, lch, rch);
}
void cha_s(int L, int R, int l, int r)
{
	int o = idx(L, R);
	if(l <= L && R <= r)
	{
		sz[o] *= 2;
		mx[o] *= 2;
		tag[o] *= 2;
		return;
	}
	int M = L + R >> 1, lch = idx(L, M), rch = idx(M + 1, R);
	push_down(o, lch, rch);
	if(l <= M)
		cha_s(L, M, l, r);
	if(r > M)
		cha_s(M + 1, R, l, r);
	push_up(o, lch, rch);
}
LL que(int L, int R, int l, int r)
{
	int o = idx(L, R);
	if(l <= L && R <= r)
		return mx[o];
	int M = L + R >> 1, lch = idx(L, M), rch = idx(M + 1, R);
	push_down(o, lch, rch);
	LL ret = 0;
	if(l <= M)
		upd(ret, que(L, M, l, r));
	if(r > M)
		upd(ret, que(M + 1, R, l, r));
	push_up(o, lch, rch);
	return ret;
}
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &n, &m);
		init(1, n);
		while(m--)
		{
			char op[2];
			LL sL, sR;
			pair<int, LL> pL, pR;
			scanf("%s%lld%lld", op, &sL, &sR);
			pL = find(1, n, sL, 0);
			pR = find(1, n, sR, 1);
			if(op[0] == 'D')
			{
				if(pL.first == pR.first)
				{
					cha_x(1, n, pL.first, sR - sL + 1);
					continue;
				}
				cha_x(1, n, pL.first, pL.second);
				cha_x(1, n, pR.first, pR.second);
				if(pL.first + 1 <= pR.first - 1)
					cha_s(1, n, pL.first + 1, pR.first - 1);
			}
			else
			{
				if(pL.first == pR.first)
				{
					printf("%lld\n", sR - sL + 1);
					continue;
				}
				LL res = max(pL.second, pR.second);
				if(pL.first + 1 <= pR.first - 1)
					upd(res, que(1, n, pL.first + 1, pR.first - 1));
				printf("%lld\n", res);
			}
		}
	}
	return 0;
}

小记

还是被虐了。文化课成绩出乎意料的差,看来要好好学习了。

PS:题解和代码已于2015/11/15 0:21补全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值