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 123451这个环,环上的每个人都需要经过至少 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[i1][0],dp[i1][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[i1][0]+c,dp[i1][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,kdepth1)](vchildren(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][depth1]+tempchildren(u)\{v}dp[temp][max(dep1,kdep1)]。这里有点复杂和巧妙,如果不太清楚可以看看这篇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;
}
posted @ 2022-03-26 16:48  unique_pursuit  阅读(54)  评论(0)    收藏  举报