1.2贪心

1.2贪心

注意事项

贪心条件:

  • 每次的选择不能依赖后面要做的选择
  • 它的最优解包含其子问题的最优解
    贪心的证明:见书

例题:【例题4】荷马史诗

  1. 题目抽象:
    给定n个单词的出现次数,求将n个单词改为n个k进制字符串后使文章最短,且对于任意的 1 ≤ i , j ≤ n , i ≠ j 1\le i,j\le n,i\ne j 1i,jn,i=j都有 s i s_i si不是 s j s_j sj的前缀,求文章的最短长度和最长的 s i s_i si的最短长度
  2. 解析:
  • 利用树的思想,建立一个k叉树,边权表示字符对应的数字,点权表示该字符串出现的次数,深度表示字符的长度
  • 用大根堆维护k进制数的出现次数,每次取出前k小的k进制数,给他们接一个新的公共的父亲,用这个父亲代替他们重新放入堆中,进行以后的合并,不断循环至只有一个点无父亲(此为根节点)
  • 父节点的建立:点权为它的子的点权之和,深度为它的子的深度的最大值+1
  • 在建树的过程中将所以节点的出现次数相加,和为文章的总长度
  • 根节点的深度为最长字符串的最短长度
  1. 重点:
  • 本题并不用建真正的树,只是利用思想进行合并,且节点的深度与树中节点的深度恰好相反
  • 因为深度代表字符串长度,在建父节点时深度要加1
  • 为方便建树,要让 ( n − 1 ) m o d ( k − 1 ) = = 0 (n-1)mod(k-1)==0 (n1)mod(k1)==0,否则用权值为0的点补全
  • 用结构体+优先队列存点代码如下:
struct node{
	ll v;
	ll d;
	inline bool operator < (const node& b) const{
		if(v!=b.v) return v>b.v;
		else return d>b.d;
	}
};
priority_queue<node>q;
  • 重载运算符时注意要让大根堆的top(即最大值)为次数最小的点,若次数相同要深度最小的
  • 用cnt记录剩余节点数,先初始为 ≥ n \ge n n的(k-1)的倍数,每次合并 -=(k-1),当cnt==1时合并结束
  1. 代码实现:
#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int N = 1e5 + 100;

ll n, k, w[N], cnt, ans;

struct node {
    ll v;
    ll d;
    inline bool operator<(const node& b) const {
        if (v != b.v)
            return v > b.v;
        else
            return d > b.d;
    }
};
priority_queue<node> q;

int main() {
#ifndef ONLINE_JUDGE
    freopen("a.in", "r", stdin);
    freopen("a.out", "w", stdout);
#endif
    scanf("%lld%lld", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &w[i]);
        node tmp;
        tmp.v = (ll)w[i];
        tmp.d = 0;  //初始:n个叶子,子树dep==0,权值为w[i]
        q.push(tmp);
    }

    cnt = n;
    if ((n - 1) % (k - 1)) {  //若n-1不是k-1的整数倍,就用权值为0的点补全
        cnt += (k - 1) - ((n - 1) % (k - 1));
    }
    for (int i = 1; i <= cnt - n; i++) {
        node tmp;
        tmp.v = 0ll;
        tmp.d = 0;
        q.push(tmp);
    }

    while (cnt != 1) {  //当无父亲的节点不止一个时,即树未建完
        ll val = 0ll;
        ll dep = 0;
        for (int i = 1; i <= k; i++) {
            node tot = q.top();
            val += (ll)tot.v;
            dep = max(dep, tot.d);
            q.pop();
        }
        cnt -= k - 1;
        ans += val;
        node tmp;
        tmp.v = val;
        tmp.d = dep + 1;  //新建父亲,子树高度是子的最大高度+1
        q.push(tmp);
    }
    printf("%lld\n%lld\n", ans, q.top().d);
    return 0;
}

终于写完了~~
为什么要有这样让人崩溃的题啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值