Codeforces Round #699 (Div. 2) (A ~ F)6题全,超高质量良心题解【每日亿题】2021/2/6

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


爱了爱了

Codeforces Round #699 (Div. 2) (A、B、C)【每日亿题】2021/2/6

这套题的前三题都不难,但是 C 题大模拟可调死我了,导致E没时间写,赛后没一会就过样例了,丢下去睡了一觉起来一交就A了…大晚上的脑子不太清醒

下次就不该死磕,多看看后面的题,说不定就可以雨露均沾地爆零呢👀

不然两万人场,四题手速快就能排三十多名了…

比赛链接:https://codeforces.com/contest/1481

A - Space Navigation

Problem A Space Navigation

你现在在一个平面直角坐标系的原点 ( 0 , 0 ) (0, 0) (0,0),你想要坐飞船去点 ( x , y ) (x,y) (x,y) ,输入一个字符串,表示你的行动指令,如果是 U 就是你能向上走一格,同理 D L R 分别表示下,左,右。行动指令的行动你可以删去一些,问你最后能否到达目标点。

Solution

因为可以少跑几步,所以我们直接计算一下最大能跑多远,然后目标在范围内的就能到。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;
typedef long long ll;
typedef int itn;
const int N = 500007;

int n, m, t;
int k, q, x, y;
itn a[N];
itn b[N];
string s;
int main()
{
    scanf("%d", &t);
    while(t -- ) {
        scanf("%d%d", &x, &y);
        cin >> s;

        int l = 0, r = 0, u = 0, d = 0;
        for(int i = 0; i < s.length(); ++ i) {
            if(s[i] == 'R') r ++ ;
            if(s[i] == 'D') d ++ ;
            if(s[i] == 'L') l ++ ;
            if(s[i] == 'U') u ++ ;
         }
         if(x >= 0 && y >= 0 && u >= y && r >= x) {
            puts("YES");
         }
         else if(x >= 0 && y <= 0 && d >= abs(y) && r >= x){
            puts("YES");
         }
         else if(x <= 0 && y >= 0 && l >= abs(x) && u >= y) {
            puts("YES");
         }
         else if(x <= 0 && y <= 0 && l >= abs(x) && d >= abs(y)) {
            puts("YES");
         }
         else puts("NO");
    }
    return 0;
}

B - New Colony

n n n 座山,输入这 n n n 座山的高度 h i h_i hi,你在第一座山上,有 k k k 块石头,你依次在第一座山上,丢下这些石头。

石头会按照下面的行为走(假设当前石头在第 i i i 座山上):

  • h [ i ] ≥ h [ i + 1 ] h[i] \ge h[i + 1] h[i]h[i+1] 则石头会滚到 i + 1 i+1 i+1 座山上,并且根据 i + 1 i+1 i+1 座山的情况,继续走。若到达最后一座山上(第 n n n 座山) ,就会掉进回收站里。
  • h [ i ] < h [ i + 1 ] h[i] <h[i + 1] h[i]<h[i+1] 石头会停下来,掉在第 i i i 座山上,并且留下来,使得第 i i i 座山的高度 + 1 +1 +1

请问第 k k k 个石头会在哪里?若在回收站里输出 -1 ,否则输出所在山的编号。

1 ≤ t ≤ 100 , 1 ≤ n ≤ 100 , 1 ≤ k ≤ 1 0 9 , 1 ≤ h i ≤ 100 1 \le t \le 100,1 \le n \le 100,1 \le k \le 10^9,1 \le h_i \le 100 1t100,1n100,1k109,1hi100

Solution

k k k 很大很大,但是 h h h n n n 都很小,所以可以直接暴力模拟即可。

反正一旦到了最后一座山就直接输出 -1 即可。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;
const int N = 5e7 + 7, mod = 1e9 + 7;
typedef long long ll;
typedef int itn;

itn n, m, q, d, k;
itn t;
int h[N];

void solve()
{
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; ++ i) {
        scanf("%d", &h[i]);
    }
    itn now = -1;
    for(int i = 1; i <= k; ++ i) {
        for(int j = 0; j < n; ++ j) {
            if(j + 1 == n) {
                puts("-1");
                return ;
            }
            else if(h[j] < h[j + 1]) {
                now = j + 1;
                h[j] ++ ;
                break;
            }
        }
    }
    printf("%d\n", now);
}

int main()
{
    scanf("%d", &t);
    while(t -- ) {
        solve();
    }
    return 0;
}

C - Fence Painting

有一排编号为 1 ⋯ n 1\cdots n 1n 栅栏,原本的颜色是 a a a 数组,小 A 想要把栅栏的颜色从 a a a 数组染成编号一一对应的 b b b 数组。有 m m m 个油漆工,第 i i i 个人,能将任意一个且仅能将任意一个栅栏染成颜色 c i c_i ci m m m 个油漆工按照 1 ⋯ m 1\cdots m 1m 的顺序,依次前来刷漆,每个人必须刷一块栅栏。问你最后能否将原先的栅栏刷成 b b b

若能,输出 YES ,并输出一种油漆工们每个人刷的栅栏的编号的方案。若不能,输出 NO

1 ≤ t ≤ 1 0 4 , 1 ≤ n , m ≤ 1 0 5 , 1 ≤ a i , b i , c i ≤ n 1 \le t \le 10^4,1 \le n, m \le 10^5,1 \le a_i ,b_i,c_i\le n 1t104,1n,m105,1ai,bi,cin

Solution

直接模拟就好了,我们多的颜色都涂到最后一个油漆工要涂的那一块就行了,反正最后会被覆盖的 ~

我tm洋洋洒洒写了100多行,调了半个多小时,开了三个计数数组,结果标程一个vector,几十行就搞定了,我还是太菜了…
我自己的sb代码就不放了,赛后我学习标程的写法,写了一个简便的代码
果然最有效的debug方法还是梳理好思路,把每句代码的用意都想清楚,不要口胡…

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <unordered_map>

using namespace std;
typedef long long ll;
typedef int itn;
const int N = 5e5 + 7, mod = 1e9 + 7;
const ll INF = 1e18 + 7;

itn n, m, t, k, q;
itn a[N], b[N], c[N];
vector<int> v[N];
int ans[N];

void solve()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++ i)
        v[i].clear();
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &b[i]);
        if(a[i] != b[i]) {
            v[b[i]].push_back(i);
        }
    }

    for(int i = 1; i <= m; ++ i) {
        scanf("%d", &c[i]);
    }

    itn last = -1;
    if((int)v[c[m]].size() > 0) {
        last = v[c[m]].back();
        v[c[m]].pop_back();
    }
    else {
        for(int i = 1; i <= n; ++ i) {
            if(b[i] == c[m]) {
                last = i;
                break;
            }
        }
    }
    if(last == -1) {
        puts("NO");
        return ;
    }
    ans[m] = last;

    for(int i = 1; i < m; ++ i) {
        if((int)v[c[i]].size() == 0) {
            ans[i] = last;
        }
        else {
            ans[i] = v[c[i]].back();
            v[c[i]].pop_back();
        }
    }
    for(int i = 1; i <= n; ++ i) {
        if((int)v[i].size() > 0) {
            puts("NO");
            return ;
        }
    }
    puts("YES");
    for(int i = 1; i <= m; ++ i) {
        printf("%d%s", ans[i], i == m ? "\n" : " ");
    }

    return ;
}

int main()
{
    scanf("%d", &t);
    while(t -- ) {
        solve();
    }
    return 0;
}

D - AB Graph

给你一张有向图,其中任意两个点之间都有两条方向相反的有向边连接,也就是完全有向图。其中每条边标有字符 a a a 或者 b b b ,现要求你找出一条长度为 m m m 的路径,使得该路径经过的字符序列是一个回文字符串。

Solution

待更,先睡午觉(

E - Sorting Books

一排书架上有 n n n 本书排成一排,每本书上有一个颜色 a i a_i ai,你可以每次将一本书移动到书架的最右端,如果书架上的书,颜色相同的书都排到了一块,我们就认为他是漂亮的,请问将这个书架通过上面的那一种操作排成漂亮的书架,最少需要几次操作?

Solution

其实是超级简单的一道题 ~

首先根据题意,先不考虑最优解,我们直接全部往右乱扔就一定能满足,但是不一定是最优解,解决最优解问题,很明显要么贪心,要么DP,要么贪心 + DP。

首先考虑贪心策略,我们一上来最直观的感受就是,如果有一种颜色,没有完全在一块,中间参杂着其他颜色的书,但是这种颜色的书的数量非常非常的多,很明显我们就可以贪心地把这种颜色中间的书移走,这些这种颜色的书就会自动合并到一块,肯定比把这种颜色的很多很多的书一个一个丢到最右边要来的快,这一点很容易想到。

所以我们来尝试找一下,有没有这种颜色的书,或者好几种,只要区间不重叠就好,数量比它中间其他颜色的书的数量还要多,也就是找到若干个区间,但是直接找区间有点困难,题目只需要我们输出最少操作次数即可,所以在有了这个完美的贪心策略以后,我们考虑DP来模拟这个过程。

我们设 f [ i ] f[i] f[i] 表示 i ⋯ n i\cdots n in 区间里不需要移动的书的最大数量,很显然答案就是 n − f [ 1 ] n-f[1] nf[1]。除去不需要移动的,把剩下的按照颜色的分类丢到右边就行了。

我们想要找到的是区间里书数量最多的颜色,并且可以是很多种区间不重叠的颜色,要先找区间,所以我们先存一下每种颜色的出现的左右区间 l i l_i li r i r_i ri ,因为要找数量最多的那一种,所以我们再使用 c n t cnt cnt 数组存一下每种颜色出现的次数。因为现在考虑的是逆序 DP,所以再存一下 c n t _ p o s t i \text cnt\_post_i cnt_posti ,表示的是 i ⋯ n i\cdots n in 之间每种颜色的书的数量。

考虑转移方程:

(1) f   [ i ] = max ⁡ { f   [ i ] , f [ i + 1 ] } \tt f\ [i] = \max\{f\ [i], f[i + 1]\} f [i]=max{f [i],f[i+1]}

(2) f [ i ] = max ⁡ { f [ i ] , c n t _ p o s t [ a [ i ] ] } \tt f[i] = \max\{f [i], {cnt\_post} [ a[ i]]\} f[i]=max{f[i],cnt_post[a[i]]}

我们取 i ⋯ n i\cdots n in 里当前最优的一种颜色,也就是只选择一个区间的情况。为什么不能写成 f [ i ] = max ⁡ { f [ i ] , c n t _ p o s t [ a [ i ] ] + f [ r [ a [ i ] ] + 1 ] } \tt f[i] = \max\{f [i], {cnt\_post} [ a[ i]]+f[r[a[i]]+1]\} f[i]=max{f[i],cnt_post[a[i]]+f[r[a[i]]+1]} 呢,也就是为什么我们不能一块加上当前颜色区间右边的最优解呢?这样看上去好像没什么毛病呀?

参见样例:

5
1 2 2 1 3
2

会发现如果在没有完全完整的区间的基础之上更新,就会导致几个区间重叠,得到实际上是错误的 “最优解”

(3)当 l [ a [ i ] ] = i \tt l[a[i]]=i l[a[i]]=i时, f [ i ] = m a x { c n t [ a [ i ] ] + f [ r [ a [ i ] ] + 1 ] } \tt f[i]=max\{cnt[a[i]]+f[r[a[i]]+1]\} f[i]=max{cnt[a[i]]+f[r[a[i]]+1]}

这里我们已经完全走完了颜色 a [ i ] \tt a[i] a[i] 的整个区间,可以跟其他区间合并以找到最优解,也不会导致区间重叠。

这个转移方程实际上就是选择区间 [ l a i , r a i ] [l_{a_i},r_{a_i}] [lai,rai] 里,颜色 a i a_i ai 不移动,因为 f [ i ] f[i] f[i] 存的是 i ⋯ n i\cdots n in 里不需要移动的书的最大数量,所以要加上 r a i r_{a_i} rai 右边的 f f f ,应该很好理解 ~

Code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>

using namespace std;
const int N = 5e5 + 7, mod = 1e9 + 7;
typedef long long ll;
typedef int itn;

itn a[N], l[N], r[N];
int cnt[N], cnt_post[N];
itn n, m;
int f[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
        if(l[a[i]] == 0) l[a[i]] = i;
        r[a[i]] = i;
        cnt[a[i]] ++ ;
    }

    for(int i = n; i >= 1; -- i) {
        f[i] = f[i + 1];
        cnt_post[a[i]] ++ ;
        if(i == l[a[i]])
            f[i] = max(f[i], f[r[a[i]] + 1] + cnt[a[i]]);
        else f[i] = max(f[i], cnt_post[a[i]]);
    }
    printf("%d\n", n - f[1]);
    return 0;
}

当然,我们上面使用逆序来求,实际上正序逆序没有任何区别 ~


牛逼群友用线段树搞出来了…

wxyyyds

%%%

我还没看懂啥意思

#include <iostream>

namespace wxy{
const int N = 5e5 + 5,inf = 1e9 + 5;
#define int long long
int a[N],f[N],lst[N],fst[N],pre[N],cnt[N],n;
struct node{int l,r,add,max;}t[N << 2];
inline void pushup(int u){t[u].max = std::max(t[u << 1].max,t[u << 1 | 1].max);}
inline void pushdown(int u){
    if (t[u].add){
        t[u << 1].max += t[u].add; t[u << 1 | 1].max += t[u].add;
        t[u << 1].add = t[u << 1 | 1].add = t[u].add; t[u].add = 0; 
    }
}
inline void build(int u,int l,int r){
    t[u].l = l; t[u].r = r; t[u].add = 0;
    if (l == r){t[u].max = cnt[l]; return;}
    int mid = l + r >> 1;
    build(u << 1,l,mid);
    build(u << 1 | 1,mid + 1,r);
    pushup(u);
}
inline void cge(int u,int l,int r,int v){
    if (t[u].l == l && t[u].r == r){
        t[u].max += v; t[u].add += v;
        return;
    }
    int mid = t[u].l + t[u].r >> 1;
    pushdown(u);
    if (r <= mid) cge(u << 1,l,r,v);
    else if (l > mid) cge(u << 1 | 1,l,r,v);
    else {cge(u << 1,l,mid,v); cge(u << 1 | 1,mid + 1,r,v);}
    pushup(u);
}
inline int query(int u,int l,int r){
    if (t[u].l == l && t[u].r == r) return t[u].max;
    pushdown(u);
    int mid = t[u].l + t[u].r >> 1;
    if (r <= mid) return query(u << 1,l,r);
    else if (l > mid) return query(u << 1 | 1,l,r);
    else return std::max(query(u << 1,l,mid),query(u << 1 | 1,mid + 1,r));
}
inline void main(){
    std::cin >> n;
    for (int i = 1; i <= n; i++) std::cin >> a[i];
    for (int i = 1; i <= n; i++){
        pre[i] = lst[a[i]];
        lst[a[i]] = i;
        cnt[a[i]]++;
    }
    int pos = n;
    for (int i = n; i >= 1; i--){
        if (a[i] == a[n]) pos = i;
        else break;
    }
    int cr = cnt[a[n]] - (n - pos + 1);
    for (int i = 1; i <= n; i++)
        if (i != a[n]) cr += cnt[i];
    for (int i = 1; i <= n; i++)
        if (fst[a[i]] == 0) fst[a[i]] = i;
    int premax = 0;
    for (int i = 1; i <= n; i++){
        if (fst[a[i]] == i){
            f[i] = 1;
            f[i] = std::max(f[i],premax + 1);
        }else{
            f[i] = f[pre[i]] + 1;
        }
        if (lst[a[i]] == i) premax = std::max(premax,f[i]);
    }
    int ct = 0;
    int ans = n - f[n];
    for (int i = 1; i <= n; i++){
        if (a[i] == a[n]) continue;
        if (lst[a[i]] == i) ans = std::min(ans,cr - f[i]);
    }
    for (int i = 1; i <= n; i++)
        if (cnt[i] == 0) cnt[i] = -inf;
    build(1,1,n);  
    for (int i = 1; i <= n; i++){
        cge(1,a[i],a[i],-1);
        if (lst[a[i]] != i) continue;
        if (a[i]-1>=1)cge(1,1,a[i]-1,f[i]);
        if (a[i]+1<=n)cge(1,a[i]+1,n,f[i]);
        ans = std::min(ans,n - query(1,1,n));
        if (a[i]-1>=1)cge(1,1,a[i]-1,-f[i]);
        if (a[i]+1<=n)cge(1,a[i]+1,n,-f[i]);
    }
   std::cout << ans;
}
}signed main(){wxy::main(); return 0;}

F - AB Tree

Problem F - AB Tree

输入 n n n, x x x,给你一棵 n n n 个结点根为 1 1 1 的树,请你为每一个结点分配一个字符 a 或者 b。使得字符 a 的数量为 x x x 字符 b 的数量为 n − x n-x nx

定义结点 v v v 上的字符串:

  • 若结点 v v v 是根结点,则结点 v v v 的上的字符串为你为根结点分配的字符
  • 否则,结点 v v v 上的字符串就是父结点上的字符串的末尾加上 结点 v v v 上面分配的字符。

请你为每个结点上分配字符,满足所有结点上的字符串的种类最少。

Solution

看上去又是一个贪心 + DP(没完了是吧

还是先分析一下题目有什么性质,比如这个字符串是什么,很明显,就是从每个结点的最上层的祖先开始(实际上就是根结点)一直到该结点组成的简单路径上经过的每个结点组成的字符串。我们发现字符串的长度也就是路径的长度,那么最长也就是树上最长的简单路径的长度 —— 树的直径 d i s t \tt dist dist。最长的的字符串的长度也就是直径 d i s t \tt dist dist + 1 \tt 1 1 。也就是所有结点上的字符串的长度不会超过 d i s t \tt dist dist + 1 \tt 1 1 ,也就意味着所有的字符串最多有 d i s t \tt dist dist + 1 \tt 1 1 层。很明显,上层,也就是离根结点近的结点,出现在字符串里的次数最多,是好多字符串的基础(前排),所以我们可以想到,前面的尽量每一层(每一个字符串的前缀)都相同,这样最后总的字符串的种数会更小。

然后我们这里的直径 d i s t \tt dist dist 严格意义上不能说是树的直径,因为字符串是从根结点开始的,所以应该是树的深度,也就是根结点的深度为 1 1 1 开始往下递推就行了。

因为我们只能也必须分配 x x x 个字符 a ,和 n − x n-x nx 个字符 b ,所以我们很直观的一个贪心策略就是让上层尽可能的一致,直到某一个字符不够用为止。

我们设树的深度为 m m m

这样我们一共会有 m m m 层。

我们发现实际上每一层,都是对应的字符串的同样的位置,也可以说是同样的下标,也就是如果我们能保证每一层分配的字符相同,那么所有的字符串都会相同,也就是不同的字符串的个数就是不同长度的字符串的个数,也就是层数 m m m

我画一个图来帮助理解:

          1a
        /    \
      2b     3b
     /  \   /  \
   4a   5a 6a   7a

这道题一画图就太形象了,显然,上面的策略的是正确的

所以我们尽量去根据这个贪心策略去走。

所以我们这道题就变成了,从 m m m 层里,选择若干层,(设第 i i i 层的结点个数为 n u m i num_i numi ),选择的这些层的结点个数之和等于 x x x ,也就是 a 的个数,然后剩下的自然都是 b ,也就能保证每一层的字符都相同,也就完成了上面的贪心策略。这很显然就是一个背包问题。我们预处理出来 n u m i num_i numi ,当作背包处理即可。

然后再来分析一下有没有其他的情况:即如果这种贪心策略不能满足,那么最小的答案是啥嘞 ~

我们发现,如果贪心策略不能满足,也就是差点,那么最多也就只会出现一层的结点字符不同,也就是需要被迫分配到两种字符。那么把这一层放到哪儿很关键。我们再来贪心。

我们知道原本贪心策略完美运行的时候,我们的答案是不同长度的字符串的个数,然后根据我们最开始的贪心策略,让不同的字符越低越好,也就是让不同的字符放到最低的叶子节点,对整体的答案影响最小,那么我们让需要被迫使用两种字符那一层,放到叶子节点,找到一个叶子节点最多的那一层,丢进去,损失最小。然后因为我们实际上不同字符只有 ab 两种,也就是那一堆叶子节点,长度相同,末尾字符不同,但也只有两种情况,所以最后的答案在 m m m 的基础之上 + 1 1 1 ,也就是 m + 1 m+1 m+1

总结一下

  • 若背包可以实现选择 若干层,结点个数和为 x x x ,答案为 m m m,我们在DP的时候存一下选择方案,被装进背包里的层,为 a ,其余全部为 b
  • 若背包不能实现,答案为 m + 1 m+1 m+1 ,我们找到叶子节点最多的那一层,把叶子节点上缺的,凑不够的 a 换成 b ,其余方案数,同上一种情况输出即可。

所以我们就dfs最大求一下深度以及每层的结点个数,叶子节点个数。

然后DP求凑成 x x x 的方案,若能凑成,就回溯一下找一下DP的选择方案,就是答案。要是凑不成就是第二种情况我们就找到能凑成的最大的数,同样先找DP方案,然后因为我们的 a 有剩余,所以我们找到一个叶子节点个数大于差值的那一层,填上 a ,然后就没了…

然后就没了…

然后,要是看不懂,你飞过来打我呀

然后我在官方题解的评论区里看到可以用bitset优化,就找了一位大佬的AC代码学习了一下,借鉴了他的DP部分,把我的垃圾背包改成bitset了,挺巧妙的 ~

在这里插入图片描述

呜呜呜

哦哦哦,树是无向边,但是如何判断是不是叶子节点呢?我用的是如果前向星索引数组 head = -1 ,则没有子节点,但是因为最开始我建图连的是双向的… 所以没办法判断了。然后因为我们的dfs只需要向下走,所以我们只需要连单向边就行(这都能A10个点…以及这道题直接七十多个点,丧心病狂

wtcl
/kk

在这里插入图片描述

然后就A了…

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <bitset>
#include <vector>
#include <unordered_map>
using namespace std;

const int N = 100007, M = 500007, INF = 0x3f3f3f3f;

typedef long long ll;
typedef int itn;

int n, m, t, x;
int fa[N];
int deep[N];
int maxx;
int num[N];//每一层结点个数
int cnt;
int lson[N];//叶子节点
int head[N], ver[M], edge[M], nex[M], tot;
bitset<N> f[2007];//能否凑齐 x,使用 bitset 优化
int val[N];
unordered_map<int, int> vis;
//int vis[N];
bool ok[N];
vector<int> v[N];

void add(int x, itn y)
{
	ver[tot] = y;
	nex[tot] = head[x];
	head[x] = tot ++ ;
}

void dfs1(int x)
{
	deep[x] = deep[fa[x]] + 1;//这里根结点 deep[1] = 1 所以不用再 +1 了
	num[deep[x]] ++ ;
	maxx = max(maxx, deep[x]);//树的直径,实际上也就是最大深度
	if (head[x] == -1) //没有子节点,说明是叶子结点
		lson[deep[x]] ++ ;//每层的叶子结点个数
	for (int i = head[x]; ~i; i = nex[i]) {
		int y = ver[i];
		dfs1(y);
	}
}

void dfs2(itn x, int t)
{
	if(x == 0)
		return;
	for (int i = 0; i < (int)v[x].size(); ++ i) {
		if(val[x] > t || f[x - 1][t])
			break;
		t -= val[x], ok[v[x][i]] = true;
	}
	dfs2(x - 1, t);
}

void init()
{
    memset(head, -1, sizeof head);
    tot = 0;
}

void solve()
{
    init();
	scanf("%d%d", &n, &x);
	for (int i = 2; i <= n; ++ i) {
		scanf("%d", &fa[i]);
		//add(i, fa[i]);
		add(fa[i], i);
	}
	dfs1(1);//预处理每层结点个数
	//把抽象的树转化为一个个物品,存到 v 里,离散化一下,种类(结点个数)和个数(层数)
	//cout << "ok" << endl;
	//一共cnt种,
	for (int i = 1; i <= maxx; ++ i) {
		if (vis[num[i]]) {
           v[vis[num[i]]].push_back(i);
		}
		else {
            vis[num[i]] = ++ cnt;
            val[cnt] = num[i];
            v[cnt].push_back(i);//来找最后选了第几层
		}
	}

	f[0][0] = 1;

    for (int i = 1; i <= cnt; ++ i) {
        f[i] = f[i - 1];
		itn Size = v[i].size();
		for (int j = 1; j <= Size; j <<= 1) {
			Size -= j;
			f[i] |= (f[i] << (j * val[i]));
		}
		if(Size > 0)
			f[i] |= f[i] << (Size * val[i]);
	}

	if(f[cnt][x]) {
		printf("%d\n", maxx);
		dfs2(cnt, x);//还原dp的方案
		for (int i = 1; i <= n; ++ i) {
			putchar(ok[deep[i]] ? 'a' : 'b');
		}
	}
	//凑不齐,'a' 有剩余
	else {
		int res = INF;
		for (int i = x; i >= 0; -- i) {
			if(f[cnt][i]) {
				res = i;//找到能凑到的最大的数
				break;
			}
		}
		dfs2(cnt, res);
		int pos = -1;
		for (int i = 1; i <= maxx; ++ i)
		{
			if(!ok[i] && lson[i] >= x - res) {
				pos = i;
				break;
			}
		}

		printf("%d\n", maxx + 1);

		for (int i = 1; i <= n; ++ i) {
			if(deep[i] == pos && head[i] == -1) {
				if(res == x) {
					putchar('b');
				}
				else
					putchar('a'), ++ res;
			}
			else {
				putchar(ok[deep[i]] ? 'a' : 'b');
			}
		}
	}
	return ;
}

itn main()
{
    solve();
	return 0;
}

为啥一堆人催更,却没人点赞呢
/kk

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值