AtCoder Beginner Contest 204 A~E 题解

A - Rock-paper-scissors

三个人玩石头剪刀布平局,其中两个出的分别是$x,y$,求另一个人出的。

$0\le x,y\le 2$($0,1,2$分别表示石头,剪刀,布)

输入格式

$x,y$

输出格式

用整数格式输出答案。

样例

$x$ $y$ 输出
$0$ $1$ $2$
$0$ $0$ $0$

分析

石头剪刀布这个游戏三人平局只有两种情况(设$z$为第三个人出的):

  • $x=y=z$
  • $x\ne y\ne z$

所以,我们得出如下递推式:
$z=\begin{cases}x & (x=y)\\3-x-y & (x\ne y)\end{cases}$

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <cstdio>
using namespace std;

int main()
{
	int x, y;
	scanf("%d%d", &x, &y);
	printf("%d\n", x == y? x: 3 - x - y);
	return 0;
}

B - Nuts

有$N$棵树,第$i$棵树上有$A_i$颗果实。
一个人会从第$i$棵树摘下$\max(0,A_i-10)$颗果实。他一共会得到多少果实?

$1\le N,A_i\le 1000$

输入格式

$N$
$A_1~\dots~A_N$

输出格式

输出答案。

样例

样例输入1

1
2
3
6 17 28

样例输出1

1
25

他会从三棵树上分别摘下$0,7,18$颗果实,总共$25$颗。

样例输入2

1
2
4
8 9 10 11

样例输出2

1
1

他只会从最后一棵树上得到一颗果实。

分析

我们直接按题意模拟即可。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <cstdio>
using namespace std;

int main()
{
	int n, ans = 0;
	scanf("%d", &n);
	while(n--)
	{
		int a;
		scanf("%d", &a);
		if(a > 10) ans += a - 10;
	}
	printf("%d\n", ans);
	return 0;
}

C - Tour

一个国家有编号为$1$至$N$的$N$座城市和编号为$1$至$M$的$M$条路。
第$i$条路从城市$A_i$到$B_i$,且不能用它从城市$B_i$到$A_i$。
一个人想从起点城市开始旅行并走过若干条路(可以不走,即只游玩一个城市)并到达终点城市。
有多少种合法的起点和终点城市?如果$X\ne Y$,则$X\to Y$和$Y\to X$算作两种不同的方案。

$2\le N\le 2000$
$0\le M\le \min(2000,N(N-1))$
$1\le A_i,B_i\le N$
$A_i\ne B_i$
$(A_i,B_i)$互不相同。

输入格式

$N~M$
$A_1~B_1$
$\vdots$
$A_M~B_M$

输出格式

输出答案。

样例

样例输入1

1
2
3
4
3 3
1 2
2 3
3 2

样例输出1

1
7

有七种可行的旅游方案:$1\to1$、$1\to2$、$1\to3$、$2\to2$、$2\to3$、$3\to2$、$3\to3$。

样例输入2

1
3 0

样例输出2

1
3

有三种可行的旅游方案:$1\to1$、$2\to2$、$3\to3$。

分析

我们可以把这个国家看成一个简单有向无权图,并把每个节点作为起点跑一遍$\text{DFS}$,计算总共能到达的结点数即可。
总时间复杂度为$\mathcal O(n^2)$。

代码

注意:每次$\text{DFS}$之前一定要将$\mathrm{vis}$数组清零!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <cstdio>
#include <vector>
#define maxn 2005
using namespace std;

vector<int> G[maxn];
bool vis[maxn];

int ans;

void dfs(int v)
{
	if(vis[v]) return;
	vis[v] = true, ans ++;
	for(int u: G[v])
		dfs(u);
}

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		G[--x].push_back(--y);
	}
	ans = 0;
	for(int i=0; i<n; i++)
	{
		for(int j=0; j<n; j++)
			vis[j] = false;
		dfs(i);
	}
	printf("%d\n", ans);
	return 0;
}

D - Cooking

两个人要洗$N$个碗,其中任意一个人洗第$i$个碗需要$T_i$分钟。一个人不能同时洗多个碗。
两个人一起洗碗,洗完所有碗至少需要多少分钟?

$1\le N\le 100$
$1\le T_i\le 10^3$

输入格式

$N$
$T_1~T_2~\dots~T_N$

输出格式

输出答案。

样例

样例输入1

1
2
5
8 3 7 2 5

样例输出1

1
13

我们可以让两个人分别洗如下的碗:

  • 第一个人洗编号为$5,1$的碗。总时间为$T_5+T_1=13$分钟。
  • 第二个人洗编号为$2,4,3$的碗。总时间为$T_2+T_4+T_3=10$分钟。

总耗时为$\max(13,10)=13$分钟。

样例输入2

1
2
2
1000 1

样例输出2

1
1000

样例输入3

1
2
9
3 14 15 9 26 5 35 89 79

样例输出3

1
138

分析

这是一道经典01背包题。
题目可以直接描述为:给定序列$T$,将其分成两个子序列$A$和$B$(不要求连续),求最小的$\min(\sum A,\sum B)$。
这时,我们发现要使$\min(\sum A,\sum B)$最小,由于$\sum A+\sum B=\sum T$,所以$|\sum A-\sum B|$必须也达到最小。
我们可以将$\sum T$分成两半,其中一半为$\lfloor\frac{\sum T}2\rfloor$。这时,我们可以用dp背包解决此题:从$T$中选出一个子序列$A$,使得$\sum A\le\lfloor\frac{\sum T}2\rfloor$,这样答案就为$\sum T-\sum A$。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <cstdio>
#define maxn 105
#define maxv 100005
using namespace std;

int dp[maxv], w[maxn];

inline void setmax(int& x, int y)
{
	if(y > x) x = y;
}

int main()
{
	int n, v = 0;
	scanf("%d", &n);
	for(int i=0; i<n; i++)
	{
		scanf("%d", w + i);
		v += w[i];
	}
	int t = v;
	v >>= 1;
	for(int i=0; i<n; i++)
		for(int j=v; j>=w[i]; j--)
			setmax(dp[j], dp[j - w[i]] + w[i]);
	printf("%d\n", t - dp[v]);
	return 0;
}

E - Rush Hour 2

一个国家有$N$座城市和$M$条路。城市的编号是$1$至$N$,路的编号则为$1$至$M$。第$i$条路双向连接着城市$A_i$和$B_i$。
在这个国家中,初始时间为$0$。如果你在时间点$t$通过公路$i$,所需时间为$C_i+\lfloor\frac {D_i} {t+1}\rfloor$。
一个人想从城市$1$到达城市$N$。他在每个城市可以停留任意自然数的时间。
求这个人最早到达城市$N$的时间。如果无法到达城市$N$,输出-1

$2\le N\le 10^5$
$2\le M\le 10^5$
$1\le A_i,B_i\le N$
$0\le C_i,D_i\le 10^9$

输入格式

$N~M$
$A_1~B_1~C_1~D_1$
$\vdots$
$A_M~B_M~C_M~D_M$

输出格式

输出答案。

样例

样例输入1

1
2
2 1
1 2 2 3

样例输出1

1
4

我们可以先在城市$1$停留至时间$1$。然后,我们出发,最终到达时间$1+2+\lfloor\frac 3 {1+1}\rfloor=4$。

样例输入2

1
2
3
4
2 3
1 2 2 3
1 2 2 1
1 1 1 1

样例输出2

1
3

两个城市之间可能有多条路,一个城市也可能有到自己的路。

样例输入3

1
2
3
4 2
1 2 3 4
3 4 5 6

样例输出3

1
-1

城市$1$到城市$N$可能没有路径。

分析

我们可以把输入当成一幅无向图。其实,从一个城市到它自己的路根本没有用,所以我们直接忽略不计。
如果目前时间为$T$且我们要从城市$A_i$到$B_i$,我们可以证明,最好的整数出发时间应该是$\lfloor\sqrt D\rceil-1$($\lfloor x\rceil$表示$x$四舍五入)。
如果$\lfloor\sqrt D\rceil\le T$,我们应该等到时间$\lfloor\sqrt D\rceil-1$再出发;否则,我们直接出发。
这时,我们可以使用Dijkstra最短路算法(使用优先队列优化),这样总时间复杂度就为$\mathcal O(M\log N)$。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <cstdio>
#include <cmath>
#include <vector>
#include <queue>
#include <tuple>
#define maxn 100005
#define INF 9223372036854775807LL
using namespace std;

using Road = tuple<int, int, int>;
using LL = long long;
using pli = pair<LL, int>;

vector<Road> G[maxn];
LL dist[maxn];

int main()
{
	int n, m;
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		if(--a == --b) continue;
		G[a].emplace_back(b, c, d);
		G[b].emplace_back(a, c, d);
	}
	dist[0] = 0LL;
	for(int i=1; i<n; i++) dist[i] = INF;
	priority_queue<pli, vector<pli>, greater<pli>> q;
	q.emplace(0LL, 0);
	while(!q.empty())
	{
		auto [t, u] = q.top(); q.pop();
		if(dist[u] != t) continue;
		for(auto [v, c, d]: G[u])
		{
			LL t2 = sqrt((long double) d) - 0.5;
			if(t2 < t) t2 = t;
			t2 += LL(c) + LL(d) / (t2 + 1LL);
			if(t2 < dist[v])
				q.emplace(dist[v] = t2, v);
		}
	}
	if(dist[n - 1] == INF) puts("-1");
	else printf("%lld\n", dist[n - 1]);
	return 0;
}
使用 Hugo 构建
主题 StackJimmy 设计