D - Doing Homework

D - Doing Homework

题目入口:D - Doing Homework
杭电入口:Doing Homework
参考网站:https://blog.csdn.net/qq_37605573/article/details/78358865

储备知识

此题运用的位运算的一些技巧 在这里提前说一下 便于后面解题
i&(0x1<<j)起到的效果是 如果数字i二进制下的第j位为1返回真 为0返回假
实现原理是 0x1<<j得到的是在二进制下数字i的第j位为1其余位为0的数 和数字i相与 即可判别数字i二进制下的第j位的值

     i == 10110
0x1<<3 == 00100
      &-> 00100

i&(~(0x1<<j))起到的效果是去除ij位的1(参照上例不难证明)

解题思路

题意:有n个学科的作业要做 每一科都有自己的截止时间以及消耗时间 超期一天扣一分 求完成所有作业最少扣分
思路:Obj结构体用于存放每一学科的名称、截止时间、消耗时间
我们可以看到每组测试样例最多只有15个学科 所以我们采用二进制位运算的思想来解题(不用急 下面会详细讲到)
我们定义一个变量用于表示选择学科的状态
具体是这样的 假设有5门学科 那么2^5^ == 100000(二进制) 则2^5^-1 == 11111
每一位的1代表一门学科 这样就可以通过每一位是0还是1来表示选择学科的状态了
我们规范一下上述过程
我们有n个学科 choice == 1<<n 这样choice-1即表示最终所有学科都已学完
dp[i]用于表示到达第i个状态时扣除分数的最小值 所以我们初始化为无穷大inf(在没有选择方案前每一门都做不完 扣分到怀疑人生)
t[i]表示到写作业第i个状态已经消耗的时间
接下来我们从1choice-1遍历所有状态
这个状态我在这里形象化的列举一下
1->0...001 表示选择第一科
2->0...010 表示选择第二科
3->0...011 表示选择第一科和第二科
4->0...100 表示选择第三科
5->0...101 表示选择第一科和第三科
... 省略中间学科选择
choice-1->1...111 表示选择所有科
这样的话状态的问题就很好地明白了
接下来是具体的科目的选择 也就是每一位上学科的选择 所以j从第n-1位到第0位(至于为什么倒着来 我们一会再说)
如果我们发现具体选择的一科并没有在当前状态下 那么就无需处理直接continue
接下来我们需要引入一个变量reduce用来记录在一个特定的状态下(没有完成当前(j)学科作业 但其他学科完成情况与当前状态一致)消耗的时间加上写完当前(j)学科消耗的时间后与当前(j)学科截止时间的差值(这个差值的意义便是超期的天数 如果值小于0则说明没有超期 所扣分数自然为0)
随后就是dp过程
如果刚刚所描述的那个特定状态的扣分加上完成当前学科扣掉的分数要小于到达第i个状态下扣掉的分数 我们就更新这个到达第i个状态下扣掉的分数
因此dp[choice-1]便是最少扣分了
但是我们解题还没结束 还差个最优方案输出
这里我们就要用到path[i]来记录到达第i个状态选择最优学科的二进制坐标
我们对于输出最优方案采取递归方式 (这也就是为什么之前我们提到要逆序选择学科状态)
从第choice-1(即最终状态)选择的最优学科开始向前递归 最终出来的便是从第一个学科到最后一个学科
所以这里递归的结束条件便是当状态为0的时候
注意区分一下 Output函数里 x是状态值 path[x]是二进制坐标
到这里讲解结束

AC代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
struct Obj{
    char s[110];
    int d;
    int c;
}o[20];
const int MAXN = 1<<15|10;
const int inf = (1<<30)-1;
int t[MAXN], dp[MAXN], path[MAXN];
void Output(int x){
    if (!x) return;
    Output(x&(~(0x1<<path[x])));
    printf("%s\n", o[path[x]].s);
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--){
        int n;
        scanf("%d", &n);
        memset(t, 0, sizeof(t));
        for (int i = 0; i < n; i++)
            scanf("%s%d%d", o[i].s, &o[i].d, &o[i].c);
        int choice = 1<<n;
        for (int i = 1; i < choice; i++){
            dp[i] = inf;
            for (int j = n-1; j >= 0; j--){
                if (!(i&(0x1<<j))) continue;
                int reduce = t[i&(~(0x1<<j))]+o[j].c-o[j].d;
                if (reduce < 0) reduce = 0;
                if (dp[i] > dp[i&(~(0x1<<j))]+reduce){
                    dp[i] = dp[i&(~(0x1<<j))]+reduce;
                    t[i] = t[i&(~(0x1<<j))]+o[j].c;
                    path[i] = j;
                }
            }
        }
        printf("%d\n", dp[choice-1]);
        Output(choice-1);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值