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))
起到的效果是去除i
第j
位的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个状态已经消耗的时间
接下来我们从1
到choice-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;
}