Hello大家好,今天给大家带来的是 Codeforces Round #734 (Div. 3) 的全题目讲解。
本文链接:https://www.lanqiao.cn/questions/204012
感谢蓝桥云课的 Lqyk 同学提供的题解。
A. Polycarp and Coins
题目链接
https://codeforces.com/contest/1551/problem/A
题目大意
有一个人买东西付钱只用一元钱和二元钱, 现在他要付
n
n
n 元, 他使用一元钱的数量和二元钱数量的差值要最小, 问他付
n
n
n 元使用了多少一元钱和二元钱?
解题思路
差值最小的话一元钱和二元钱的数量比例要接近
1
:
1
1 : 1
1:1 ,那么总共三元钱, 那么每份都使用的
n
/
3
n / 3
n/3 ,最后只要判断一下剩下的钱是
0
,
1
,
2
0, 1, 2
0,1,2 元的情况即可。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
ll n;
cin >> n;
ll a = n / 3;
ll b = n / 3;
if(n % 3 == 2) b++;
else if(n % 3 == 1) a++;
cout << a << " " << b << endl;
}
return 0;
}
B1. Wonderful Coloring - 1
题目链接
https://codeforces.com/contest/1551/problem/B1
题目大意
满足以上三个条件,每种颜色被染色的字符数量是多少?
解题思路
相同的字符每种颜色最多染一个,因此只要对出现次数 $ \geq 2$ 的字符和出现一次的字符分别讨论就行。
- 出现次数 $ \geq 2$? 的字符, 每种颜色都能分到一个,答案加上这些字符的种类数。
- 出现一次的,红一个,绿一个,答案加上这些字符种类数除以二(向下取整)。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
string s;
cin >> s;
map<char, int> mp;
int ans = 0;
for(int i = 0;i < s.size();i++) mp[s[i]]++;
int cnt = 0;
for(auto i : mp){
if(i.second >= 2) ans++;
else cnt++;
}
cout << ans + cnt / 2 << endl;
}
return 0;
}
B2. Wonderful Coloring - 2
题目链接
https://codeforces.com/contest/1551/problem/B2
题目大意
用
0
?
k
0 - k
0?k 表示染色的颜色种类 (
0
0
0 表示不染色) ,求满足上述条件的染色方案。
解题思路
和
B
1
B1
B1 一样的思路,考虑将 出现次数$ \geq k$? 的数字和 出现次数
<
k
< k
<k 的分开讨论。
- 出现次数 $ \geq k$?? 的字符, 每种颜色都能分到一个, 所以把位置存储下来直接染色即可。
- 出现次数
<
k
< k
<k? 的集中到一起, 为了防止相同的数字染成同一种颜色,所以先排序在按顺序对下标染色。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 50;
struct node{
int id, val;
bool operator < (const node &p) const{
return val < p.val;
}
};
vector<int> num[maxn];
vector<node> q;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
int n, k;
cin >> n >> k;
vector<int> ans(n + 1, 0), vis(n + 1, 0), a(n + 1);
for(int i = 1;i <= n;i++) num[i].clear(); q.clear();
for(int i = 1;i <= n;i++){
cin >> a[i];
num[a[i]].push_back(i);
}
for(int i = 1;i <= n;i++){
if(num[i].size() >= k){
for(int j = 0;j < num[i].size();j++){
ans[num[i][j]] = j + 1;
}
vis[i] = 1;
}
}
for(int i = 1;i <= n;i++){
if(!vis[a[i]]){
q.push_back({i, a[i]});
}
}
sort(q.begin(), q.end());
int sum = q.size() / k * k, v = 1;
for(int i = 0;i < sum;i++){
ans[q[i].id] = v++;
if(v == k + 1) v = 1;
}
for(int i = 1;i <= n;i++){
if(ans[i] > k) cout << 0 << " ";
else cout << ans[i] << " ";
}
cout << endl;
}
return 0;
}
C. Interesting Story
题目链接
https://codeforces.com/contest/1551/problem/C
题目大意
从
n
n
n 个只包含
a
、
b
、
c
、
d
、
e
a、b、c、d、e
a、b、c、d、e 的字符串中选择若干个,使得某一个单一字符的出现次数大于其余四个总和,问最多可以选多少个字符串。
解题思路
我们可以记录每个字符串
a
、
b
、
c
、
d
、
e
a、b、c、d、e
a、b、c、d、e 出现的次数,然后枚举
a
、
b
、
c
、
d
、
e
a、b、c、d、e
a、b、c、d、e 分别作为大于其他四个字符总和的情况。
假设当前枚举
a
a
a 第
i
i
i 个字符串
a
a
a 出现的次数为
s
u
m
sum
sum, 其余四个总和为
n
o
w
now
now ,那么他们的差值
s
u
m
?
n
o
w
sum - now
sum?now 为正的时候是可以选的,为了选择更多,我们贪心的将
s
u
m
?
n
o
w
sum - now
sum?now 的值从大到小排序,不断加入字符串, 保持总和 $\geq 0 $即可。
AC代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 50;
int a[maxn], b[maxn], c[maxn], d[maxn], e[maxn];
int n;
int get_a(){
vector<ll> ans;
for(int i = 0;i < n;i++){
ll sum = a[i];
ll now = b[i] + c[i] + d[i] + e[i];
ans.push_back(sum - now);
}
sort(ans.rbegin(), ans.rend());
ll k = 0;
int cnt = 0;
for(int i = 0;i < n;i++){
if(k + ans[i] > 0LL) {
cnt++;
k += ans[i];
}
else break;
}
return cnt;
}
int get_b(){
vector<ll> ans;
for(int i = 0;i < n;i++){
ll sum = b[i];
ll now = a[i] + c[i] + d[i] + e[i];
ans.push_back(sum - now);
}
sort(ans.rbegin(), ans.rend());
ll k = 0;
int cnt = 0;
for(int i = 0;i < n;i++){
if(k + ans[i] > 0LL) {
cnt++;
k += ans[i];
}
else break;
}
return cnt;
}
int get_c(){
vector<ll> ans;
for(int i = 0;i < n;i++){
ll sum = c[i];
ll now = a[i] + b[i] + d[i] + e[i];
ans.push_back(sum - now);
}
sort(ans.rbegin(), ans.rend());
ll k = 0;
int cnt = 0;
for(int i = 0;i < n;i++){
if(k + ans[i] > 0LL) {
cnt++;
k += ans[i];
}
else break;
}
return cnt;
}
int get_d(){
vector<ll> ans;
for(int i = 0;i < n;i++){
ll sum = d[i];
ll now = a[i] + b[i] + c[i] + e[i];
ans.push_back(sum - now);
}
sort(ans.rbegin(), ans.rend());
ll k = 0;
int cnt = 0;
for(int i = 0;i < n;i++){
if(k + ans[i] > 0LL) {
cnt++;
k += ans[i];
}
else break;
}
return cnt;
}
int get_e(){
vector<ll> ans;
for(int i = 0;i < n;i++){
ll sum = e[i];
ll now = a[i] + b[i] + c[i] + d[i];
ans.push_back(sum - now);
}
sort(ans.rbegin(), ans.rend());
ll k = 0;
int cnt = 0;
for(int i = 0;i < n;i++){
if(k + ans[i] > 0LL) {
cnt++;
k += ans[i];
}
else break;
}
return cnt;
}
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
cin >> n;
for(int i = 0;i <= n;i++) a[i] = b[i] = c[i] = d[i]= e[i] = 0;
for(int i = 0;i < n;i++){
string s;
cin >> s;
for(int j = 0; j < s.size();j++){
if(s[j] == 'a') a[i]++;
else if(s[j] == 'b') b[i]++;
else if(s[j] == 'c') c[i]++;
else if(s[j] == 'd') d[i]++;
else if(s[j] == 'e') e[i]++;
}
}
int ans = 0;
ans = max(ans, get_a());
ans = max(ans, get_b());
ans = max(ans, get_c());
ans = max(ans, get_d());
ans = max(ans, get_e());
cout << ans << endl;
}
return 0;
}
D1. Domino (easy version)
题目链接
https://codeforces.com/contest/1551/problem/D1
题目大意
在 $n * m $ 的网格图放
1
×
2
1\times2
1×2 (横着)或者
2
×
1
2\times1
2×1(竖着) 的多米若骨牌,在
n
?
m
n * m
n?m 为偶数的情况下,是否能恰好放
k
k
k 个横着的多米洛骨牌。
解题思路
考虑为什么题意给的是
n
?
m
n*m
n?m 都是偶数,而不是
n
、
m
n、m
n、m 都是偶数。
当
n
、
m
n、m
n、m? 都为偶数的时候,
2
×
2
2\times2
2×2? 的方格可以任意放 $2 $ 个
1
×
2
1\times2
1×2 横着的或者
2
×
1
2 \times 1
2×1?竖着的,并且它们都是偶数成对出现的,因此我们就可以分割图面为若干个
2
×
2
2\times2
2×2的网格, 此时
k
k
k 为偶数才能满足条件。
假设若
n
n
n 为奇数, 画图不难发现必定要放
m
/
2
m/2
m/2 个横着的,同理,若
m
m
m 为奇数, 必定要放
n
/
2
n / 2
n/2 个竖着的,多出来的行列铺满横的或者竖的后就转化偶数情况。
因此只要判断总得减去必定放的是否放得下
k
k
k?个并且
k
k
k? 要大于必定放横的数量。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
int n, m, k;
cin >> n >> m >> k;
int tot = n * m / 2;
if(n % 2){
k -= m / 2;
tot -= m / 2;
}
if(m % 2) tot -= n / 2;
if(k < 0 || k % 2 || k > tot){
cout << "NO\n";
continue;
}
cout << "YES\n";
}
return 0;
}
D2. Domino (hard version)
题目链接
https://codeforces.com/contest/1551/problem/D2
题目大意
在 $n \times m $ 的网格图放
1
×
2
1\times2
1×2 (横着)或者
2
×
1
2\times1
2×1(竖着) 的多米若骨牌,在
n
×
m
n \times m
n×m 为偶数的情况下,是否能恰好放
k
k
k 个横着的多米洛骨牌,用字符输出摆放图案。
解题思路
D
1
D1
D1 的思路能讨论出来后,就按照讨论分别构造即可。
首先先判断有没有奇数的行、列,从
a
?
z
a - z
a?z? 任意找俩个个字符填充完。
然后将这多的行列暂时删去,当成 $n,m $? 都为偶数填充。
先填充
k
k
k? 个横着的,剩下的
2
×
2
2\times2
2×2 填充竖着的即可,唯一的问题就是相邻的字符会重复的问题,我们可以根据下标的奇偶关系来交替填充不同?字符,当然字符从
a
?
z
a - z
a?z 自己挑。
AC_Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 200;
char ans[maxn][maxn];
int main()
{
std::ios::sync_with_stdio(false);
int t;
cin >> t;
while(t--){
int n, m, k;
cin >> n >> m >> k;
int tot = n * m / 2;
for(int i = 1;i <= n;i++){
for(int j = 1;j <= m;j++){
ans[i][j] = '#';
}
}
int nn = n, mm = m;
if(n % 2){
k -= m / 2;
tot -= m / 2;
int cnt = 1;
for(int j = 1;j <= m;j += 2){
if(cnt % 2) ans[n][j] = ans[n][j + 1] = 'o';
else ans[n][j] = ans[n][j + 1] = 'p';
cnt++;
}
n--;
}
if(m % 2){
tot -= n / 2;
int cnt = 1;
for(int i = 1;i <= n;i += 2){
if(cnt % 2) ans[i][m] = ans[i + 1][m] = 'x';
else ans[i][m] = ans[i + 1][m] = 'y';
cnt++;
}
m--;
}
if(k < 0 || k % 2 || k > tot){
cout << "NO\n";
continue;
}
int cnt = 1;
for(int j = 1;j <= m;j += 2){
cnt ++;
for(int i = 1;i <= n;i++){
if(k != 0){
if((i + cnt) & 1) ans[i][j] = ans[i][j + 1] = 'a';
else ans[i][j] = ans[i][j + 1] = 'b';
k--;
}
if(!k) break;
}
if(!k) break;
}
for(int i = 1;i <= n;i += 2){
cnt++;
for(int j = 1;j <= m;j++){
if(ans[i][j] == '#'){
if((j + cnt) & 1) ans[i][j] = ans[i + 1][j] = 'k';
else ans[i][j] = ans[i + 1][j] = 'v';
}
}
}
cout << "YES\n";
for(int i = 1;i <= nn;i++){
for(int j = 1;j <= mm;j++){
cout << ans[i][j];
}
cout << endl;
}
}
return 0;
}
E. Fixed Points
题目链接
https://codeforces.com/contest/1551/problem/E
题目大意
给定一个长度为
n
n
n 的数组
a
a
a 和一个常数
k
k
k。
现有一种操作,操作内容为选择数组
a
a
a 中任意一个数
a
i
a_i
ai? 并删除它,删除之后
a
i
a_i
ai? 右边的数将全部向右移动一位,即
a
i
?
1
,
a
i
,
a
i
+
1
,
a
i
+
2
,
.
.
.
,
a
n
→
a
i
?
1
,
a
i
+
1
,
a
i
+
2
,
a
i
+
3
,
.
.
.
,
a
n
a_{i-1},a_i,a_{i+1},a_{i+2},...,a_n\rightarrow a_{i-1},a_{i+1},a_{i+2},a_{i+3},...,a_{n}
ai?1?,ai?,ai+1?,ai+2?,...,an?→ai?1?,ai+1?,ai+2?,ai+3?,...,an?。
定义
b
1
,
b
2
,
.
.
.
,
b
m
b_1,b_2,...,b_m
b1?,b2?,...,bm? 为
a
a
a 进行若干次操作后的数组,问最少要进行多少次操作,可以使得
b
i
=
i
b_i=i
bi?=i 的个数大于等于
k
k
k。
解题思路
因为本题的数据范围不大
1
≤
k
≤
n
≤
2
×
1
0
3
1\leq k\leq n\leq 2\times10^3
1≤k≤n≤2×103,所以不难想到用
d
p
dp
dp 来解决。
定义
f
i
,
j
f_{i,j}
fi,j? 表示数组
a
a
a 的前
i
i
i 个数,删除了
j
j
j 个后,
b
h
=
h
(
1
≤
h
≤
i
)
b_h=h(1\leq h\leq i)
bh?=h(1≤h≤i) 的最大个数。
于是当前的这个数
a
i
a_i
ai?,有两种决策:
- 删除
a
i
a_i
ai?:删除
a
i
a_i
ai? 后
a
i
a_i
ai? 就带来任何贡献,即
f
i
,
j
=
f
i
?
1
,
j
?
1
f_{i,j} = f_{i-1,j-1}
fi,j?=fi?1,j?1?。
- 不删除
a
i
a_i
ai?:此时已经删除了
j
j
j 个数,那么
a
i
a_i
ai? 的位置为
i
?
j
i-j
i?j,所以
f
i
,
j
=
f
i
?
1
,
j
+
[
a
i
=
=
i
?
j
]
f_{i,j} = f_{i-1,j} + [a_i == i-j]
fi,j?=fi?1,j?+[ai?==i?j]
最后从小到大枚举删除的个数
j
j
j,若
f
n
,
j
≥
k
f_{n,j} \geq k
fn,j?≥k,则该
j
j
j 为答案。
AC_Code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10;
int n , k , a[N], f[N][N];
signed main()
{
int T = 1;
cin >> T;
while(T --)
{
cin >> n >> k;
memset(f , 0 , sizeof(int) * n * n);
for(int i = 1 ; i <= n ; i ++) cin >> a[i];
for(int i = 1 ; i <= n ; i ++)
{
for(int j = 0 ; j <= i ; j ++)
{
if(!j) f[i][j] = f[i - 1][j] + (a[i] == i - j);
else f[i][j] = max(f[i - 1][j - 1], f[i - 1][j] + (a[i] == i - j));
}
}
bool flag = 0;
for(int j = 0 ; j <= n ; j ++) if(f[n][j] >= k)
{
cout << j << '\n';
flag = 1;
break ;
}
if(!flag) cout << -1 << '\n';
}
return 0;
}
F. Equidistant Vertices
题目链接
https://codeforces.com/contest/1551/problem/F
题目大意
给定一个包含
n
n
n 个节点的树和一个常数
K
K
K,要求你树中选择
K
K
K 个节点并放入集合,使得集合内任意两点之间的距离都相等,问有多少种不同的选择方法。
解题思路
- 当
K
=
2
K = 2
K=2 时,无论怎么选取,集合内都只存在一种距离,满足条件,所以答案为
C
n
2
C_{n}^{2}
Cn2?。
- 当
K
>
2
K > 2
K>2 时,则必然存在一个点
u
u
u,使得集合内的点分别位于
u
u
u 的不同分支上,且到
u
u
u 的距离相等(证明略),我们定义这样的
u
u
u 为集合的中心
我们以
u
u
u 作为集合的中心,枚举集合内的点到
u
u
u 的距离,设当前枚举的距离为
d
d
d。
那么在以
u
u
u 为根的树里,如果超过
K
K
K 个分支中存在与
u
u
u 的距离大于等于
d
d
d 的节点,则我们可以选择从这些分支中挑选
K
K
K 个作为方案。定义这些分支为有效分支,并有效分支的个数为
c
n
t
cnt
cnt,那么能带来的方案数为
C
c
n
t
K
C_{cnt}^{K}
CcntK??不对,因为某些有效分支中可能不仅仅存在一个与
u
u
u 距离大于等于
d
d
d 的节点,它们所能带来的方案数不能被忽略,也不好直接计算,于是考虑
d
p
dp
dp!
- 我们先定义
f
[
u
]
[
j
]
f[u][j]
f[u][j] 表示在以
v
v
v 为根的子树中,与
v
v
v 距离为
j
j
j 的节点的个数,那么:
- 我们再定义
d
p
[
u
]
[
j
]
[
k
]
dp[u][j][k]
dp[u][j][k] 表示以
u
u
u 为根,从
u
u
u 的前
j
j
j 个有效分支中,选择
k
k
k 个有效分支的方案数,那么对于第
j
j
j 个有效分支(设为
v
v
v),有以下两种决策:
-
选择第
j
j
j 个有效分支:
d
p
[
u
]
[
j
]
[
k
]
=
d
p
[
u
]
[
j
?
1
]
[
k
?
1
]
×
C
f
[
v
]
[
d
?
1
]
1
=
d
p
[
u
]
[
j
?
1
]
[
k
?
1
]
×
f
[
v
]
[
d
?
1
]
dp[u][j][k] = dp[u][j - 1][k - 1] \times C_{f[v][d-1]}^{1} = dp[u][j - 1][k - 1] \times f[v][d-1]
dp[u][j][k]=dp[u][j?1][k?1]×Cf[v][d?1]1?=dp[u][j?1][k?1]×f[v][d?1].
f
[
v
]
[
d
?
1
]
f[v][d-1]
f[v][d?1] 表示
v
v
v 的子树中与
v
v
v 距离大于
d
?
1
d-1
d?1 的节点个数,即与
u
u
u 距离大于等于
d
d
d 的节点个数
C
f
[
v
]
[
d
]
1
C_{f[v][d]}^1
Cf[v][d]1? 表示从这些节点中任意挑选一个
-
不选择第
j
j
j 个有效分支:
d
p
[
u
]
[
j
]
[
k
]
=
d
p
[
u
]
[
j
?
1
]
[
k
]
dp[u][j][k] = dp[u][j - 1][k]
dp[u][j][k]=dp[u][j?1][k]
对于每次枚举的距离
d
d
d ,
u
u
u 对答案的贡献为
d
p
[
u
]
[
c
n
t
]
[
K
]
dp[u][cnt][K]
dp[u][cnt][K]
AC_Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e2 + 10, mod = 1e9 + 7;
struct Edge
{
int nex, to;
} edge[N << 1];
int head[N] , TOT;
int n , K , f[N][N];
long long res , dp[N][N][N];
void add_edge(int u, int v)
{
edge[++ TOT].nex = head[u];
edge[TOT].to = v;
head[u] = TOT;
}
void init()
{
for(int i = 0 ; i <= n + 1 ; i ++) head[i] = 0;
TOT = 0;
}
void dfs(int u, int far)
{
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far) continue ;
dfs(v, u);
for(int j = 1 ; j <= n ; j ++) f[u][j] += f[v][j - 1];
}
f[u][0] = 1;
}
void dfs1(int u , int far)
{
memset(dp , 0 , sizeof(int) * n * n * n);
for(int j = 1 ; j <= n ; j ++)
{
dp[u][0][0] = 1;
int cnt = 0;
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far)
{
if(j == 1)
{
dp[u][++ cnt][0] = 1;
for(int k = cnt ; k >= 1 ; k --) dp[u][cnt][k] = (dp[u][cnt - 1][k] + dp[u][cnt - 1][k - 1]) % mod;
}
else
{
if(f[far][j - 1] - f[u][j - 2] > 0)
{
dp[u][++ cnt][0] = 1;
for(int k = cnt ; k >= 1 ; k --) dp[u][cnt][k] = (dp[u][cnt - 1][k] + dp[u][cnt - 1][k - 1] * (f[far][j - 1] - f[u][j - 2]) % mod) % mod;
}
}
}
else
{
if(f[v][j - 1])
{
dp[u][++ cnt][0] = 1;
for(int k = cnt ; k >= 1 ; k --) dp[u][cnt][k] = (dp[u][cnt - 1][k] + dp[u][cnt - 1][k - 1] * f[v][j - 1] % mod) % mod;
}
}
}
res += dp[u][cnt][K] , res %= mod;
}
if(u != 1)
{
for(int j = n ; j >= 1 ; j --)
{
if(j == 1) f[u][j] += f[far][0];
else f[u][j] += (f[far][j - 1] - f[u][j - 2]);
}
}
for(int i = head[u] ; i ; i = edge[i].nex)
{
int v = edge[i].to;
if(v == far) continue ;
dfs1(v, u);
}
}
signed main()
{
int T = 1;
cin >> T;
while(T --)
{
init();
res = 0;
cin >> n >> K ;
for(int i = 1 ; i <= n ; i ++) for(int j = 1 ; j <= n + 1; j ++) f[i][j] = 0;
for(int i = 1 ; i < n ; i ++)
{
int u, v;
cin >> u >> v;
add_edge(u, v), add_edge(v, u);
}
if(K == 2)
{
cout << n * (n - 1) / 2 << '\n';
continue ;
}
dfs(1, 0) , dfs1(1, 0);
cout << res << '\n';
}
return 0;
}
如有编写错误或者疑惑请评论告诉我,我会第一时间回应的(如果代码在终测中挂掉了,也请评论告知我谢谢!!!)
本文作者:Lqyk
本文链接:https://www.lanqiao.cn/questions/204012
版权声明:本作品采用知识共享署名-禁止演绎 2.5 中国大陆许可协议进行许可。
|