GoodCoder666的个人博客

AtCoder Beginner Contest 204 A~E 题解

2021-06-07 · 11 min read
C++ 算法竞赛

A - Rock-paper-scissors

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

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

输入格式

x,yx,y

输出格式

用整数格式输出答案。

样例

xx yy 输出
00 11 22
00 00 00

分析

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

  • x=y=zx=y=z
  • xyzx\ne y\ne z

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

代码

#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

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

1N,Ai10001\le N,A_i\le 1000

输入格式

NN
A1  ANA_1~\dots~A_N

输出格式

输出答案。

样例

样例输入1

3
6 17 28

样例输出1

25

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

样例输入2

4
8 9 10 11

样例输出2

1

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

分析

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

代码

#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

一个国家有编号为11NNNN座城市和编号为11MMMM条路。
ii条路从城市AiA_iBiB_i,且不能用它从城市BiB_iAiA_i
一个人想从起点城市开始旅行并走过若干条路(可以不走,即只游玩一个城市)并到达终点城市。
有多少种合法的起点和终点城市?如果XYX\ne Y,则XYX\to YYXY\to X算作两种不同的方案。

2N20002\le N\le 2000
0Mmin(2000,N(N1))0\le M\le \min(2000,N(N-1))
1Ai,BiN1\le A_i,B_i\le N
AiBiA_i\ne B_i
(Ai,Bi)(A_i,B_i)互不相同。

输入格式

N MN~M
A1 B1A_1~B_1
\vdots
AM BMA_M~B_M

输出格式

输出答案。

样例

样例输入1

3 3
1 2
2 3
3 2

样例输出1

7

有七种可行的旅游方案:111\to1121\to2131\to3222\to2232\to3323\to2333\to3

样例输入2

3 0

样例输出2

3

有三种可行的旅游方案:111\to1222\to2333\to3

分析

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

代码

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

#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

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

1N1001\le N\le 100
1Ti1031\le T_i\le 10^3

输入格式

NN
T1 T2  TNT_1~T_2~\dots~T_N

输出格式

输出答案。

样例

样例输入1

5
8 3 7 2 5

样例输出1

13

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

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

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

样例输入2

2
1000 1

样例输出2

1000

样例输入3

9
3 14 15 9 26 5 35 89 79

样例输出3

138

分析

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

代码

#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

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

2N1052\le N\le 10^5
2M1052\le M\le 10^5
1Ai,BiN1\le A_i,B_i\le N
0Ci,Di1090\le C_i,D_i\le 10^9

输入格式

N MN~M
A1 B1 C1 D1A_1~B_1~C_1~D_1
\vdots
AM BM CM DMA_M~B_M~C_M~D_M

输出格式

输出答案。

样例

样例输入1

2 1
1 2 2 3

样例输出1

4

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

样例输入2

2 3
1 2 2 3
1 2 2 1
1 1 1 1

样例输出2

3

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

样例输入3

4 2
1 2 3 4
3 4 5 6

样例输出3

-1

城市11到城市NN可能没有路径。

分析

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

代码

#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;
}