Codeforces Round #622(Div. 2) 总结&题解
题解
A. Fast Food Restaurant
原题目
我看到这道题的第一反应,其实不是暴力,而是贪心。(神奇)
首先我们先给客人上只包含一种菜的菜肴,然后再上两种菜的,最后再上三种菜的。(所以答案最大为7)
计算只有一道菜的代码很简单,见下:
int a[3];
cin >> a[0] >> a[1] >> a[2];
int ans = 0;
for(int i = 0; i < 3; i++)
if(a[i] > 0)
{
ans++;
a[i]--;
}
两道菜的情况枚举即可:
for(int i = 0; i < 3; i++)
for(int j = i + 1; j < 3; j++)
if(a[i] > 0 && a[j] > 0)
{
ans++;
a[i]--;
a[j]--;
}
三道菜最简单:
if(a[0] > 0 && a[1] > 0 && a[2] > 0)
ans++;
兴奋的试了样例以后……WA
因为我们这个方法是有反例的:2 2 3
程序会输出4,而answer应该是5。
我们来分析一下原因:
第一块代码跑过之后,数组变成1 1 2
,ans更新为3.
第二块代码:当
i
=
0
,
j
=
1
i = 0, j = 1
i=0,j=1时,数组变成0 0 2
,ans更新为4
随后,ans再也不会更新了。
可我们的最优方案为:
1 1 2
→
\rightarrow
→0 1 1
→
\rightarrow
→0 0 0
,此时ans = 5.
怎么来解决这个问题呢?
很简单,排序就行了。
(我个人觉得特判反而麻烦)
sort(a, a + 3, cmp);//从大到小
因为我们把最大的数放在前面,所以我们在选菜肴的时候会优先拆分最大的数,这样就保证是最优解。
附上AC代码:
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int x,int y)
{
return x > y;
}
int main()
{
int t;
cin >> t;
while(t--)
{
int a[3];
cin >> a[0] >> a[1] >> a[2];
int ans = 0;
for(int i = 0; i < 3; i++)
if(a[i] > 0)
{
ans++;
a[i]--;
}
sort(a, a + 3, cmp);
for(int i = 0; i < 3; i++)
for(int j = i + 1; j < 3; j++)
if(a[i] > 0 && a[j] > 0)
{
ans++;
a[i]--;
a[j]--;
}
if(a[0] > 0 && a[1] > 0 && a[2] > 0)
ans++;
cout << ans << endl;
}
return 0;
}
时间复杂度 O ( 1 ) O(1) O(1)
然而参加比赛的时候这道题我43分钟才交上去QAQ
B. Different Rules
结论
这题是道货真价实的数学题。
首先,结论如下。
1.
M
I
N
_
P
L
A
C
E
=
m
a
x
(
1
,
m
i
n
(
n
,
x
+
y
−
n
+
1
)
)
1.MIN\_PLACE=max(1,min(n,x+y-n+1))
1.MIN_PLACE=max(1,min(n,x+y−n+1))
2.
M
A
X
_
P
L
A
C
E
=
m
i
n
(
n
,
x
+
y
−
1
)
2.MAX\_PLACE=min(n,x+y−1)
2.MAX_PLACE=min(n,x+y−1)
其实这道题的思考过程不好引入,就是找规律得来的。
为了减少分类讨论的情况个数,我们默认
x
<
y
x<y
x<y。
有关 M A X _ P L A C E MAX\_PLACE MAX_PLACE
我们首先先来探究 M A X _ P L A C E MAX\_PLACE MAX_PLACE:
Case 1:
不难发现,当 x + y ≥ n + 1 x+y \ge n+1 x+y≥n+1时, M A X _ P L A C E = n MAX\_PLACE=n MAX_PLACE=n
Case 2:
然后,当
x
+
y
<
n
+
1
x+y<n+1
x+y<n+1时,为了让place尽可能大,需要让其它的sum能排在它前面的就排在它前面。
来个具体的例子
1 2 3 4 5 6
first round
1 2 3 4 5 6
second round
设
x
=
2
,
y
=
4
x=2,y=4
x=2,y=4,可以列出最佳的方案为
第一轮名次 | 第二轮名次 | sum | 最终名次 |
---|---|---|---|
1 | 5 | 6 | 5 |
2 | 4 | 6 | 5 |
3 | 3 | 6 | 5 |
4 | 2 | 6 | 5 |
5 | 1 | 6 | 5 |
6 | 6 | 12 | 6 |
最后我们可以得出:凡是第一轮名次
≥
x
+
y
\ge x+y
≥x+y的都不可能排到x+y的前面,剩下的都可以排到x+y的前面。
而第一轮名次
≥
x
+
y
\ge x+y
≥x+y的人的数量为
n
−
(
x
+
y
)
+
1
n-(x+y)+1
n−(x+y)+1,
所以第一轮名次
<
x
+
y
<x+y
<x+y的人的数量为
x
+
y
−
1
x+y-1
x+y−1,
所以
M
A
X
_
P
L
A
C
E
=
x
+
y
−
1
MAX\_PLACE=x+y-1
MAX_PLACE=x+y−1.
把上面这两个式子合并即可得到
M
A
X
_
P
L
A
C
E
=
m
i
n
(
n
,
x
+
y
−
1
)
MAX\_PLACE=min(n,x+y−1)
MAX_PLACE=min(n,x+y−1)
有关 M I N _ P L A C E MIN\_PLACE MIN_PLACE
接下来我们来探究
M
I
N
_
P
L
A
C
E
MIN\_PLACE
MIN_PLACE:
(case特别多)
Case 1:
首先,还是不难发现当 x + y < n + 1 x+y<n+1 x+y<n+1时 M I N _ P L A C E = 1 MIN\_PLACE=1 MIN_PLACE=1
Case 2:
其次,当
x
+
y
≥
n
+
1
;
y
≠
n
x+y\ge n+1;y \neq n
x+y≥n+1;y=n时,
获得第一轮前
k
k
k 个名次
(
k
≤
x
+
y
−
n
)
(k \le x+y-n)
(k≤x+y−n)的人最终一定
s
u
m
≤
x
+
y
−
n
+
n
=
x
+
y
sum \le x+y-n+n=x+y
sum≤x+y−n+n=x+y(因为n就是它们第二轮可以获得的最高名次),
所以
M
I
N
_
P
L
A
C
E
=
x
+
y
−
n
+
1
MIN\_PLACE=x+y-n+1
MIN_PLACE=x+y−n+1。
Case 3:
然后,当
x
+
y
≥
n
+
1
;
y
=
n
x+y\ge n+1;y=n
x+y≥n+1;y=n时,
获得第一轮前
k
k
k 个名次
(
k
≤
x
+
y
−
n
)
(k \le x+y-n)
(k≤x+y−n)的人最终一定
s
u
m
≤
x
+
y
−
n
+
n
−
1
=
x
+
y
−
1
sum \le x+y-n+n-1=x+y-1
sum≤x+y−n+n−1=x+y−1,即
s
u
m
<
x
+
y
sum<x+y
sum<x+y(因为n+1就是它们第二轮可以获得的最高名次),
所以
M
I
N
_
P
L
A
C
E
=
x
+
y
−
n
+
1
MIN\_PLACE=x+y-n+1
MIN_PLACE=x+y−n+1。
Case 4:
分类讨论似乎到这里就结束了诶OvO!
然而这里有个比较容易被忽略的细节,我们此处要特判
x
=
y
=
n
x=y=n
x=y=n的情况,因为这种情况出来的
M
I
N
_
P
L
A
C
E
MIN\_PLACE
MIN_PLACE为
n
+
1
n+1
n+1,但是这样的名次好比
t
a
n
90
°
tan 90°
tan90°——不存在的!不过这个特判非常简单,结果显而易见——
n
n
n。
将Case 2~4合并为
M
I
N
_
P
L
A
C
E
=
m
i
n
(
n
,
x
+
y
−
n
+
1
)
MIN\_PLACE=min(n,x+y-n+1)
MIN_PLACE=min(n,x+y−n+1)。
最后将所有Case合并为
M
I
N
_
P
L
A
C
E
=
m
a
x
(
1
,
m
i
n
(
n
,
x
+
y
−
n
+
1
)
)
MIN\_PLACE=max(1,min(n,x+y-n+1))
MIN_PLACE=max(1,min(n,x+y−n+1))
其他
以上只是探究和思考过程,具体证明请看此处,这里不再赘述。
这道题比较考验思维,大家 感性理解 理性分析即可。
附上最终代码:
#include<iostream>
using namespace std;
void solve()
{
int n, x, y;
cin >> n >> x >> y;
cout << min(max(x + y - n + 1, 1), n) << ' ';
cout << min(x + y - 1, n) << endl;
return;
}
int main()
{
int t;
cin >> t;
while(t--)
solve();
return 0;
}
时间复杂度 O ( 1 ) O(1) O(1)
C1. Skyscraper(easy version)
原题目
这道题官网上给了两种解题思路,而我更喜欢第二种。
思路是这样滴:
因为不能在一座楼的两边都有比它高的楼,所以不难想到,让高度总和最高的最佳方案一定是“单峰”的。用专业化的语言来讲,Formally,我们建立一个数组 a a a存放最后每座大楼的最终高度,那么最佳方案一定满足如下条件:存在一个数 k ( 1 ≤ k ≤ n ) k(1 \le k \le n) k(1≤k≤n),使得 a 1 ≤ a 2 ≤ a 3 ≤ . . . ≤ a k a_1 \le a_2 \le a_3 \le ... \le a_k a1≤a2≤a3≤...≤ak且 a k ≥ a k + 1 ≥ a k + 2 ≥ . . . ≥ a n a_k \ge a_{k+1} \ge a_{k+2} \ge ... \ge a_n ak≥ak+1≥ak+2≥...≥an。此时我们把 a k a_k ak称为“峰顶”(peak)。
所以我们只需要枚举峰顶(其实就是最高的楼),然后算出它左边和右边的所有楼的最高高度,再在所有的
s
u
m
sum
sum里取最优就可以了。
因为
n
≤
1000
n \le 1000
n≤1000,所以
O
(
n
2
)
O(n^2)
O(n2)的算法绰绰有余。
代码如下:(注意开long long
)
#include<iostream>
using namespace std;
const int maxn = 1005;
long long maxx, a[maxn], m[maxn], res[maxn];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i++)
cin >> m[i];
for(int i = 1; i <= n; i++)//枚举peak
{
long long sum = 0;
a[i] = m[i];//这一座楼建得最高
sum += a[i];
long long l, r;
l = r = a[i];
for(int j = i - 1; j > 0; j--)//计算左边所有楼的最高高度
{
l = min(l, m[j]);
a[j] = l;
sum += a[j];
}
for(int j = i + 1; j <= n; j++)//计算右边
{
r = min(r, m[j]);
a[j] = r;
sum += a[j];
}
if(sum > maxx)//更新答案
{
maxx = sum;
for(int j = 1; j <= n; j++)
res[j] = a[j];
}
}
for(int i = 1; i <= n; i++)
cout << res[i] << ' ';
cout << endl;
return 0;
}
时间复杂度 O ( n 2 ) O(n^2) O(n2)
特别说明
由于本人能力有限,题解只写到C1。
总结
自己这次比赛成绩不理想,主要原因在于思维不够灵活以及知识的缺失。当下最需要做的是按时完成老师布置的作业并且复习巩固以前所学的知识。并通过写题解总结发现不足,并加深对解题思路的理解。
坚持不懈,终有出头之日!!!
另外,这篇题解如有不足之处,还请见谅,可以向我提意见。
(当然得是知道如何联系我的人才可以 😃 )
感谢观看