AtCoder Beginner Contest 274 A~E 题解

46 篇文章 7 订阅
43 篇文章 6 订阅


吐槽:这比赛名字为啥没有英文版。。。

A - Batting Average

题目大意

给定整数 A , B A,B A,B,输出 B A \frac BA AB,保留三位小数。

1 ≤ A ≤ 10 1\le A\le 10 1A10
0 ≤ B ≤ A 0\le B\le A 0BA

分析

签到题,使用printfcout格式化输出即可。

代码

#include <cstdio>
using namespace std;

int main()
{
	int a, b;
	scanf("%d%d", &a, &b);
	printf("%.3Lf\n", (long double)b / a);
	return 0;
}

B - Line Sensor

题目大意

给定一个 H × W H\times W H×W的网格,每个方格内都是.#
求每一列的#的个数,分别输出。

1 ≤ H , W ≤ 1000 1\le H,W\le 1000 1H,W1000

分析

开一个数组ans[W],存储每一列的#的个数。输入时统计一下即可。

代码

#include <cstdio>
#define maxn 1005
using namespace std;

char s[maxn];
int ans[maxn];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while(n--)
	{
		scanf("%s", s);
		for(int i=0; i<m; i++)
			if(s[i] == '#')
				ans[i] ++;
	}
	for(int i=0; i<m; i++)
		printf("%d ", ans[i]);
	return 0;
}

C - Ameba

题目大意

有一棵由 2 N + 1 2N+1 2N+1个结点组成的树,根结点是 1 1 1

整棵树用一个序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,,AN)表示:

  • 结点 A i A_i Ai 2 i 2i 2i 2 i + 1 2i+1 2i+1的父亲。

求每个结点的深度。

1 ≤ N ≤ 2 × 1 0 5 1\le N\le 2\times 10^5 1N2×105
1 ≤ A i ≤ 2 i − 1 1\le A_i\le 2i-1 1Ai2i1

解法1

根据题意构造树的邻接表,从根结点 1 1 1开始向下搜索,从而推出每个结点的深度。

#include <cstdio>
#include <vector>
#define maxn 200005
using namespace std;

vector<int> G[maxn << 1];
int dep[maxn << 1];

void dfs(int v, int par)
{
	for(int u: G[v])
		if(u != par)
		{
			dep[u] = dep[v] + 1;
			dfs(u, v);
		}
}

int main()
{
	int n;
	scanf("%d", &n);
	for(int i=1; i<=n; i++)
	{
		int x;
		scanf("%d", &x);
		G[x].push_back(i << 1);
		G[x].push_back(i << 1 | 1);
	}
	dep[1] = 0;
	dfs(1, -1);
	for(int i=1; i<=(n<<1)+1; i++)
		printf("%d\n", dep[i]);
	return 0;
}

解法2(最优解)

我们从解法 1 1 1进一步考虑:由于 1 ≤ A i ≤ 2 i − 1 1\le A_i\le 2i-1 1Ai2i1,所以 A i A_i Ai一定在 2 i 2i 2i 2 i + 1 2i+1 2i+1前被处理,那么直接在输入时计算depth[2*i] = depth[2*i+1] = depth[A[i]] + 1即可。

#include <cstdio>
#include <vector>
#define maxn 200005
using namespace std;

int dep[maxn << 1];

int main()
{
	int n;
	scanf("%d", &n);
	for(int i=1; i<=n; i++)
	{
		int x;
		scanf("%d", &x);
		dep[i << 1] = dep[i << 1 | 1] = dep[x] + 1;
	}
	for(int i=1; i<=(n<<1)+1; i++)
		printf("%d\n", dep[i]);
	return 0;
}

D - Robot Arms 2

题目大意

给定整数 N N N和序列 A = ( A 1 , A 2 , … , A N ) A=(A_1,A_2,\dots,A_N) A=(A1,A2,,AN),能否在平面直角坐标系中通过 N N N步从 ( 0 , 0 ) (0,0) (0,0)走到 ( x , y ) (x,y) (x,y)?每一步如下:

  • 1 1 1步:从 ( 0 , 0 ) (0,0) (0,0)走到 ( A 1 , 0 ) (A_1,0) (A1,0)(向右前进 A 1 A_1 A1格)。
  • i i i步( i > 1 i>1 i>1)先左转或右转 90 ° 90\degree 90°,再前进 A i A_i Ai格。

2 ≤ N ≤ 1 0 3 2\le N\le 10^3 2N103
1 ≤ A i ≤ 10 1\le A_i\le 10 1Ai10
− 1 0 4 ≤ x , y ≤ 1 0 4 -10^4\le x,y\le 10^4 104x,y104

分析

先考虑另一个问题:

在一维坐标系中,从 s s s开始进行 N N N次位移,第 i i i次的操作如下:
→   \to~  选择左移或者右移 A i A_i Ai个长度单位,即坐标加上 A i A_i Ai或者减去 A i A_i Ai
N N N次操作后是否能到达终点 t t t注意:必须为最终到达,中途经过不算数!

很容易想到使用一个简单的 DP \text{DP} DP,令 f ( i , j ) f(i,j) f(i,j)表示前 i i i次操作后是否能达到 j j j 0 0 0 1 1 1),转移显而易见: f ( i , j ) = f ( i − 1 , j − A i ) ∨ f ( i − 1 , j + A i ) f(i,j)=f(i-1,j-A_i)\vee f(i-1,j+A_i) f(i,j)=f(i1,jAi)f(i1,j+Ai)
但是这样的时间复杂度很高,高达 O ( N k ) \mathcal O(Nk) O(Nk),其中 k k k为坐标系大小。

稍加思考会发现,只有小部分坐标能真正达到,其余都没有必要参与转移,所以使用set进行存储, S i S_i Si表示前 i i i次操作后能到达的坐标集合,利用 S i = ( S i − 1 + A i ) ∪ ( S i − 1 − A i ) S_i=(S_{i-1}+A_i)\cup(S_{i-1}-A_i) Si=(Si1+Ai)(Si1Ai)进行转移即可。

代码:

inline bool check(vector<int>& v, int start, int target)
{
	set<int> s;
	s.insert(start);
	for(int d: v)
	{
		set<int> ls = s;
		s.clear();
		for(int x: ls)
			s.insert(x + d), s.insert(x - d);
	}
	return s.count(target);
}

然后回到原来的问题,发现由于 x x x y y y两个坐标互不影响,所以把两个坐标轴分别独立出来是没有问题的,可以转换为刚才的子问题:

  • 对于 x x x坐标,起始位置为 A 1 A_1 A1,终点为 x x x,移动序列为 A 3 , A 5 , … A_3,A_5,\dots A3,A5,
  • 对于 y y y坐标,起始位置为 0 0 0,终点为 y y y,移动序列为 A 2 , A 4 , … A_2,A_4,\dots A2,A4,

只要两个子问题的条件都满足,那么一定存在一种可行的操作序列来满足原题的要求。
至此,问题得到解决。

代码

#include <cstdio>
#include <vector>
#include <set>
using namespace std;

inline bool check(vector<int>& v, int start, int target)
{
	set<int> s;
	s.insert(start);
	for(int d: v)
	{
		set<int> ls = s;
		s.clear();
		for(int x: ls)
			s.insert(x + d), s.insert(x - d);
	}
	return s.count(target);
}

int main()
{
	int n, x, y;
	scanf("%d%d%d", &n, &x, &y);
	vector<int> a(n);
	for(int& t: a) scanf("%d", &t);
	vector<int> dx;
	for(int i=2; i<n; i+=2)
		dx.push_back(a[i]);
	if(!check(dx, a[0], x)) { puts("No"); return 0; }
	vector<int> dy;
	for(int i=1; i<n; i+=2)
		dy.push_back(a[i]);
	puts(check(dy, 0, y)? "Yes": "No");
	return 0;
}

E - Booster

题目大意

在平面直角坐标系中,有 N N N个城市和 M M M个箱子。城市 i i i位于坐标 ( X i , Y i ) (X_i,Y_i) (Xi,Yi),箱子 i i i则在坐标 ( P i , Q i ) (P_i,Q_i) (Pi,Qi)

Takahashi现在要从原点 ( 0 , 0 ) (0,0) (0,0)开始访问 N N N个城市,中途箱子可去可不去。他初始的速度为 1 1 1,每碰到一个箱子都可以将速度提升至原先的两倍(每个箱子只能加速一次)。

至少要用多少时间,才能将 N N N个城市都访问至少一次?

分析

参考AtCoder 官方题解的做法,这里不详细解释。

代码

#include <cstdio>
#include <cmath>
#define maxn 17
using namespace std;

inline double ppow(int x) { return 1.0 / (1 << __builtin_popcount(x)); }
inline void setmin(double& x, double y)
{
	if(y < x) x = y;
}

double x[maxn], y[maxn], dp[maxn][1 << maxn];

int main()
{
	// Input
	int n, m;
	scanf("%d%d", &n, &m);
	m += n;
	for(int i=0; i<m; i++)
		scanf("%lf%lf", x + i, y + i);
	int mx = 1 << m;
	for(int i=0; i<m; i++)
		for(int s=0; s<mx; s++)
			dp[i][s] = 1e18;

	// DP: Initial state
	for(int i=0; i<m; i++)
		dp[i][1 << i] = hypot(x[i], y[i]);

	// DP: Transfer
	for(int s=1; s<mx; s++)
	{
		double coef = ppow(s >> n);
		for(int i=0; i<m; i++)
		{
			if(!(s >> i & 1)) continue;
			for(int j=0; j<m; j++)
			{
				if(s >> j & 1) continue;
				setmin(dp[j][s | (1 << j)],
					dp[i][s] + hypot(x[i] - x[j], y[i] - y[j])*coef);
			}
		}
	}

	// Output
	double ans = 1e18;
	for(int i=0, t=1<<n; i<m; i++)
		for(int s=t-1; s<mx; s+=t)
			setmin(ans, dp[i][s] + dp[i][1 << i] * ppow(s >> n));
	printf("%.10f\n", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值