Codeforces Round #595 (Div. 3) 题解
文章目录
A. Yet Another Dividing into Teams
-
题意
给定 n n n个人的能力值,你需要将他们分组,使得组数最少且每组之间的成员能力值相差大于 1 1 1。 -
解题思路
不难发现,当所有人的能力值出现相差大于 1 1 1时,此时需要一组就可以了。
如果出现相差值为 1 1 1,此时我们需要新开一组,而是不是两组就够了呢?我们来简单分析一下:对于 i , i + 1 , i + 2 , i + 3 , i + 4... i,i+1,i+2,i+3,i+4... i,i+1,i+2,i+3,i+4...我们可以让 i + 1 , i + 3... i+1,i+3... i+1,i+3...为一组, i , i + 2 , i + 4 i,i+2,i+4 i,i+2,i+4为一组。这样相邻的对我们总能用两组就能分开。
所以问题的实质就是判断是否存在能力值相差为 1 1 1即可。 -
AC代码
/**
*@filename:A
*@author: pursuit
*@created: 2021-08-27 10:25
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 100 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t,n,a[N];
void solve(){
sort(a + 1, a + n + 1);
int cnt = 1;
for(int i = 1; i < n; ++ i){
if(a[i] + 1 == a[i + 1]){
++ cnt;
break;
}
}
cout << cnt << endl;
}
int main(){
cin >> t;
while(t -- ){
cin >> n;
for(int i = 1; i <= n; ++ i){
cin >> a[i];
}
solve();
}
return 0;
}
B1,B2. Books Exchange (easy and hard version)
-
题意
初始时,每个人都有自己的一本书。每天结束的时候,第 i i i个人会将他当前手上的书给第 p i p_i pi个人。问第 i i i个人至少需要多少天能拿回自己初始的书。 -
解题思路
对于简单版本,我们可以dfs深搜得到,这很容易实现,即对于每个人进行搜索判断何时回到自己。时间复杂度为 O ( n 2 ) O(n^2) O(n2)。该方法代码这里不提供,请读者自行实现或者搜索其他题解。
对于困难版本,由于 n n n高达 2 × 1 0 5 2\times 10^5 2×105,故上述爆搜是行不通的。我们可以转化一下,以上操作实际上再找一个环,那么对于 1 − 2 − 3 − 4 − 5 − 1 1-2-3-4-5-1 1−2−3−4−5−1这个环,环上的每个人都需要经过至少 5 5 5天才能拿回自己的书,即环的大小。
所以我们只需要找出每个人所处的环大小即可。这有很多种方法实现,这里通过并查集实现。 -
AC代码
/**
*@filename:B1
*@author: pursuit
*@created: 2021-08-27 10:33
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t,n,p[N],father[N],num[N];
int find(int x){
int r = x;
while(father[r] != r)r = father[r];
int i = x, j;
while(father[i] != r){
j = father[i];
father[i] = r;
i = j;
}
return r;
}
void solve(){
int fu,fv;
memset(num,0,sizeof(num));
for(int i = 1; i <= n; ++ i){
fu = find(i), fv = find(p[i]);
if(fu != fv){
father[fu] = fv;
}
}
//确定环的大小。
for(int i = 1; i <= n; ++ i)num[find(i)] ++;
for(int i = 1; i <= n; ++ i){
printf("%d%c", num[find(i)], i == n ? '\n' : ' ');
}
}
int main(){
scanf("%d", &t);
while(t -- ){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d", &p[i]);
father[i] = i;
}
solve();
}
return 0;
}
C1,C2.Good Numbers (easy and hard version)
-
题意
给你一个正整数n。你真的很喜欢好的数字,所以你想找到最小的好数字大于或等于n。 如果正整数可以表示为3的不同幂的和(即不允许3的幂的重复),则称为好整数。 -
解题思路
对于简单版本而言,我们发现, 3 9 3^9 39实际上就超过了 1 e 4 1e4 1e4。所以我们的选择只有 9 9 9位。显然这可以通过爆搜枚举实现,时间复杂度为 O ( n ) O(n) O(n)。
对于困难版本而言,简单的爆搜枚举不太现实。回到问题本身,我们需要构造出一个最小的正整数大于或等于 n n n,且不允许 3 3 3的次幂重复,转化为三进制即为 10001100 10001100 10001100这种类型。我们就可以将 n n n转化为 3 3 3进制,那么位数上出现 2 2 2的是不符合我们情况的,我们需要进位,由于进位了,所以是一定大于 n n n的,根据贪心思想,则进位处之后的数全变为 0 0 0即可。注意:我们进位要寻找前面第一个为 0 0 0的位置,这样才可以加 1 1 1,那么之后的 1 1 1都要变为 0 0 0。 -
C1-爆搜AC代码
/**
*@filename:C1
*@author: pursuit
*@created: 2021-08-27 10:55
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t,n,power[10], minn;
void init(){
power[0] = 1;
for(int i = 1; i < 10; ++ i){
power[i] = power[i - 1] * 3;
}
}
void dfs(int x,int step){
//x为当前的值。
if(x >= n){
minn = min(minn, x);
return;
}
if(step >= 10)return;
for(int i = step; i < 10; ++ i){
dfs(x + power[i], i + 1);
}
}
void solve(){
minn = INF;
dfs(0,0);
cout << minn << endl;
}
int main(){
cin >> t;
init();
while(t -- ){
cin >> n;
solve();
}
return 0;
}
- C2-贪心代码
/**
*@filename:C2
*@author: pursuit
*@created: 2021-08-27 11:15
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 1e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int t,bit[40];//3进制位。
ll n,ans;
void solve(){
memset(bit,0,sizeof(bit));
int cnt = 0;
while(n){
bit[cnt ++] = n % 3;
n /= 3;
}
for(int i = cnt; i >= 0; -- i){
if(bit[i] == 2){
//当遇到2,后面的位都变为0,前面选择一个非1位变0,为了最小,后面的都变为0.
for(int j = i; j >= 0; -- j){
bit[j] = 0;
}
for(int j = i + 1; j <= cnt; ++ j){
if(bit[j] == 1){
bit[j] = 0;
}
else if(bit[j] == 0){
bit[j] = 1;
break;
}
}
break;
}
}
ll ans = 0,temp = 1;
for(int i = 0; i <= cnt; ++ i){
if(bit[i])ans += temp;
temp *= 3;
}
cout << ans << endl;
}
int main(){
cin >> t;
while(t -- ){
cin >> n;
solve();
}
return 0;
}
D1,D2. Too Many Segments (easy and hard version)
- 题意
数轴上有 n n n条线段,现在要求数轴上的点不能存在超过 k k k条线段覆盖它,如果存在超过的情况,那么删除最少的线段数量。 - 解题思路
首先我们需要清楚一个问题,如何有效的处理这些线段?当然是对这些线段进行排序,以左端点优先,右端点其次从小到大排序即可,注意保存线段的编号。这样做的好处是我们可以有序处理。
那么对于简单版本,我们可以贪心模拟操作,即顺序遍历这些线段,判断是否可以添加覆盖,如果不行的话我们需要删除一条线段来更替此线段覆盖,由于左边是已经满足的,所以为了尽量少造成后续点的覆盖问题,所以我们需要删除右端点最右的线段。 这个线段的编号我们需要自己去维护,这样问题即可解决,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
对于困难版本,上述方法显然是不行的,因为我们做了一个模拟点的覆盖操作,这实际上是非常多余的。如果我们能将时间复杂度降为 O ( n log n ) O(n\log n) O(nlogn),那么是可以通过此题的。
所以这里我们需要用set容器维护靠右的端点,同时取消点的覆盖操作,转化为遍历点,这样的好处是我们可以对此点产生贡献的一同处理,然后删除掉最靠右的线段直到该点的覆盖次数等于 k k k。
所以在这个set中我们要做的就是更新点的覆盖线段,删除过期线段以及保存需要删除的线段。由于有set维护,其时间复杂度达到了 O ( n log n ) O(n\log n) O(nlogn),可以通过。 - 贪心-D1AC代码
/**
*@filename:D1
*@author: pursuit
*@created: 2021-08-27 12:08
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 200 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n,k,cnt[N];//cnt[i]表示i顶点被覆盖了多少次。
bool vis[N];//vis[i]表示线段i被使用过。
int l,r;
struct node{
int l,r,id;
bool operator < (const node &A){
if(l == A.l){
return r < A.r;
}
return l < A.l;
}
}a[N];
bool check(int x){
for(int i = a[x].l; i <= a[x].r; ++ i){
if(cnt[i] + 1 > k){
//此时超过了。
return false;
}
}
vis[x] = true;
for(int i = a[x].l; i <= a[x].r; ++ i){
++ cnt[i];
}
return true;
}
void solve(){
sort(a + 1, a + n + 1);
vector<int> res;
int cur = 0;//保存当前能用的线段中右端点最右的线段。
for(int i = 1; i <= n; ++ i){
if(check(i)){
if(i == 1)cur = 1;
else if(a[i].r >= a[cur].r)cur = i;
}
else{
//删除右端点最右的线段。
if(a[i].r > a[cur].r)continue;
vis[cur] = false, vis[i] = true;
for(int j = a[cur].l; j <= a[cur].r; ++ j){
-- cnt[j];
}
for(int j = a[i].l; j <= a[i].r; ++ j){
++ cnt[j];
}
//更新右端点。
int maxx = 0;
for(int j = 1; j <= i; ++ j){
if(vis[j] && a[j].r >= maxx){
maxx = a[j].r, cur = j;
}
}
}
}
for(int i = 1; i <= n; ++ i){
if(!vis[i]){
res.push_back(a[i].id);
}
}
printf("%d\n", (int)res.size());
sort(res.begin(), res.end());
for(auto &x : res){
printf("%d ", x);
}
puts("");
}
int main(){
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++ i){
scanf("%d%d", &a[i].l, &a[i].r);
a[i].id = i;
}
solve();
return 0;
}
- 贪心+set维护-D2AC代码
/**
*@filename:D1
*@author: pursuit
*@created: 2021-08-27 12:08
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n,k,cnt[N];//cnt[i]表示i顶点被覆盖了多少次。
bool vis[N];//vis[i]表示线段i被使用过。
int l,r;
struct node{
int l,r,id;
bool operator < (const node &A){
if(l == A.l)return r < A.r;
return l < A.l;
}
}a[N];
set<pii> s;
void solve(){
sort(a + 1, a + n + 1);
vector<int> res;
int idx = 1;
pii temp;
for(int i = l; i <= r; ++ i){
//将小于该界限的放入set集合中。
while(idx <= n && a[idx].l <= i){
s.insert({a[idx].r, a[idx].id});
++ idx;
}
//剔除过期元素。
while(s.size() && s.begin() -> first < i){
s.erase(s.begin());
}
while(s.size() > k){
temp = *(-- s.end());
res.push_back(temp.second);
s.erase(temp);
}
}
sort(res.begin(), res.end());
printf("%d\n", res.size());
for(auto &x : res){
printf("%d ", x);
}
puts("");
}
int main(){
scanf("%d%d", &n, &k);
l = INF,r = 0;
for(int i = 1; i <= n; ++ i){
scanf("%d%d", &a[i].l, &a[i].r);
r = max(a[i].r, r),l = min(a[i].l, l);
a[i].id = i;
}
solve();
return 0;
}
E. By Elevator or Stairs?
-
题意
有 n n n个楼层,需要你求出从第 1 1 1层到达第 i i i层的最短时间。 -
解题思路
一个简单 d p dp dp,考虑到可以使用楼梯和电梯。所以我们可以定义 d p [ i ] [ 0 ] dp[i][0] dp[i][0]为到达第 i + 1 i + 1 i+1层,且从第 i i i层到第 i + 1 i + 1 i+1层使用楼梯的最短时间, d p [ i ] [ 1 ] dp[i][1] dp[i][1]为到达第 i + 1 i + 1 i+1层,且从第 i i i层到第 i + 1 i + 1 i+1层使用电梯的最短时间。那么状态转移方程也可得:
d p [ i ] [ 0 ] = m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] ) + a [ i ] dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + a[i] dp[i][0]=min(dp[i−1][0],dp[i−1][1])+a[i]
d p [ i ] [ 1 ] = m i n ( d p [ i − 1 ] [ 0 ] + c , d p [ i − 1 ] [ 1 ] ) + b [ i ] dp[i][1] = min(dp[i - 1][0] + c, dp[i - 1][1]) + b[i] dp[i][1]=min(dp[i−1][0]+c,dp[i−1][1])+b[i]
时间复杂度为 O ( n ) O(n) O(n)。
需要注意的就是等电梯需要多花费 c c c,所以从楼梯过渡电梯是需要 c c c代价的。 -
AC代码
/**
*@filename:E
*@author: pursuit
*@created: 2021-08-27 12:25
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 2e5 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n,c,a[N],b[N];
int dp[N][2];
void solve(){
dp[1][0] = a[1];
dp[1][1] = b[1] + c;
for(int i = 2; i < n; ++ i){
dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + a[i];
dp[i][1] = min(dp[i - 1][0] + c, dp[i - 1][1]) + b[i];
}
for(int i = 0; i < n; ++ i){
printf("%d%c", min(dp[i][0], dp[i][1]), i == n - 1 ? '\n' : ' ');
}
}
int main(){
scanf("%d%d", &n, &c);
for(int i = 1; i < n; ++ i){
scanf("%d", &a[i]);
}
for(int i = 1; i < n; ++ i){
scanf("%d", &b[i]);
}
solve();
return 0;
}
F. Maximum Weight Subset
-
题意
给你一颗 n n n个顶点的树,其中顶点 v v v的权重为 a v a_v av。需要你找出具有最大总权重的子集,使得子集中的各点距离大于 k k k。 -
解题思路
树形dp经典题。由于是无向树,所以我们可以设定树根为 1 1 1。同时为了操作方便,我们可以增加 k k k使得两点之间的距离满足 ≥ k \geq k ≥k的条件即可。对于每个点,有取和不取两种选择,那么我们可以设如果我们取的深度最小的顶点的深度至少为 d e p t h depth depth,则以 u u u为子树中的子集的最大总权重为 d p [ u ] [ d e p t h ] dp[u][depth] dp[u][depth],那么答案则为 d p [ 1 ] [ 0 ] dp[1][0] dp[1][0]。
我们考虑整个状态转移过程,设当前点为 u u u,深度为 d e p t h depth depth。那么会有两种情况:即如果 d e p t h = 0 depth =0 depth=0,那么当前子树结点必须取,则 d p [ u ] [ d e p t h ] + = a [ u ] + d p [ v ] [ m a x ( 0 , k − d e p t h − 1 ) ] ( v ∈ c h i l d r e n ( u ) ) dp[u][depth] += a[u] + dp[v][max(0,k - depth - 1)](v\in children(u)) dp[u][depth]+=a[u]+dp[v][max(0,k−depth−1)](v∈children(u));否则,我们可以遍历 u u u的所有子节点,然后让其称为 u u u的子节点,这样我们就可以使得最小深度的顶点位于 v v v的子树上。然后进行状态转移: d p [ u ] [ d e p t h ] = m a x ( d p [ u ] [ d e p t h ] , d p [ v ] [ d e p t h − 1 ] + ∑ t e m p ∈ c h i l d r e n ( u ) \ { v } d p [ t e m p ] [ m a x ( d e p − 1 , k − d e p − 1 ) ] dp[u][depth] = max(dp[u][depth] ,dp[v][depth - 1] + \sum\limits_{temp \in children(u) \backslash \{v\}} dp[temp][max(dep - 1, k - dep - 1)] dp[u][depth]=max(dp[u][depth],dp[v][depth−1]+temp∈children(u)\{v}∑dp[temp][max(dep−1,k−dep−1)]。这里有点复杂和巧妙,如果不太清楚可以看看这篇blog,也可以看官方题解。
计算完所有的 d p [ u ] [ d e p t h ] dp[u][depth] dp[u][depth],我们需要清楚的是当前代表的为取最小深度的点的深度正好为 d e p t h depth depth
,而所有的 d e p t h depth depth都是符合的,所以这里我们还需要进行一次动态规划,将值转移到 d p [ u ] [ 0 ] dp[u][0] dp[u][0],即是我们最终的答案。
时间复杂度为 O ( n 3 ) O(n^3) O(n3)。 -
AC代码
/**
*@filename:F
*@author: pursuit
*@created: 2021-08-27 15:37
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N = 200 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;
struct edge{
int to,next;
}edges[N << 1];
int head[N],tot;
int n,k,a[N],dp[N][N];
int u,v;
void add(int u,int v){
edges[++ tot].to = v;
edges[tot].next = head[u];
head[u] = tot;
}
void dfs(int u,int fu){
dp[u][0] = a[u];
for(int i = head[u]; i; i = edges[i].next){
v = edges[i].to;
if(v == fu)continue;
dfs(v,u);
}
for(int depth = 0; depth < n; ++ depth){
if(depth == 0){
for(int i = head[u]; i; i = edges[i].next){
v = edges[i].to;
if(v == fu)continue;
dp[u][depth] += dp[v][max(0,k - depth - 1)];
}
}
else{
for(int i = head[u]; i; i = edges[i].next){
v = edges[i].to;
if(v == fu)continue;
int ans = dp[v][depth - 1];
for(int j = head[u]; j; j = edges[j].next){
int temp = edges[j].to;
if(temp == fu || temp == v)continue;
ans += dp[temp][max(depth - 1, k - depth - 1)];
}
dp[u][depth] = max(dp[u][depth], ans);
}
}
}
for(int depth = n - 1; depth > 0; -- depth){
dp[u][depth - 1] = max(dp[u][depth - 1], dp[u][depth]);
}
}
void solve(){
dfs(1,-1);
printf("%d\n", dp[1][0]);
}
int main(){
scanf("%d%d", &n, &k);
++ k;
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
for(int i = 1; i < n; ++ i){
scanf("%d%d", &u, &v);
add(u,v),add(v,u);
}
solve();
return 0;
}

浙公网安备 33010602011771号