今天上课cf先讲了一下树上的dp(几句话带过)
emmmROS有一说一对我们这些几乎为0基础的人来这么教真的学不会啥
ROS还是由题入手讲讲算法
于是ROS做了一下昨天留的dp的题,今天来讲一讲昨天留的”通天之分组背包“(洛谷P1757)
这道题是一道分组背包问题蛤
1、通天之分组背包 洛谷P1757
说实话ROS一开始看到这道题目真的没有太多思路,于是看了看题解
(原来ROS特别讨厌看题解,现在ROS却是不得不看题解)
先来看一组样例的数据:
45 3 10 10 1 10 5 1 50 400 2
看不懂不要紧ROS来解释解释。
45表示背包容量为45,3表示一共有三件物品。
之后3行中第一个数字为重量,第二个数字为价值,第三个数字为所属组数。
所以我们可以画图有:
然后我们还是像上一篇博客中所提到的那样,开出一维数组模拟dp。
然后我们开三层循环进行dp模拟,这三层循环从外到内分别为:组数,容量(从大到小),每一组中的每一个东西。
ROS一一解释一下子:
最外层是组数没什么好解释的,就是一组组枚举。
第二层枚举容量的原因即也同一般的dp,如不明白请看上一篇博客。至于为什么是从后往前进行枚举,这便是因为这是一个分组背包,然而不是一个分组完全背包,在使用一位数组模拟实现dp的时候只有当完全背包问题时才会从前往后枚举容量。因为物品是限量的所以一次只能选用一组中的一个物品一次,所以要从后向前枚举容量。可能有人要问了,如此模拟万一我选用了一个背包内的物品两次怎么办。别急,这不第三层循环还没解释吗。
第三层枚举的是每一组内的物品。可能会有人问万一两个物品在同一组也有可能会搜两遍呀;但是这是不可能的,原因很简单(ROS进入调试界面才搞明白):就是因为在枚举容量的时候是逆序搜索,所以我们dp这一组的时候的前面是没有更新这一组的数据的,这也就是为什么我们要把枚举容量放在上一层dp也就是为了防止在运算时的后效性。
如果还不明白第三层枚举的解释的话,ROS用自己的探索过程解释一下:
首先ROS自创了一组数据如下:
50 3 40 40 1 10 10 1 10 5 2
所以如果一个组中物品如果可以选两次的话答案应该为50,然而输出答案是这个样子的:
45
所以说明并没有选择第一组的物品两次。
原因就是想第三层解释一样由于定义全局变量所以在一开始所有数据都为0于是dp数组便在倒序枚举容量的时候无后效性。
核心dp代码:
1 if(t[i].tt[k].w<=j){ 2 dp[j]=max(dp[j-t[i].tt[k].w]+t[i].tt[k].val,dp[j]); 3 }
所以代码实现也算是比较清晰啦!
(ROS由于防止头脑混乱所以进行了循环体嵌套,但其实毫无思维含量的蛤)
代码实现:
#include<iostream> #include<cstdio> #include<queue> #define M 1050 using namespace std; int m,n; int a,b,c,tot[105],zs; int dp[1050]; struct th{ int val; int w; }; struct thing{ th tt[1010]; }; thing t[110]; int main(){ scanf("%d%d",&m,&n); for(int i=1;i<=n;i++){ scanf("%d%d%d",&a,&b,&c); if(tot[c]==0) zs++; tot[c]++; t[c].tt[tot[c]].w=a; t[c].tt[tot[c]].val=b; } for(int i=1;i<=100;i++){ //枚举组数 if(!tot[i]) continue; //如果这一组没有物品则跳过 for(int j=m;j>=0;j--){ //枚举每容量 for(int k=1;k<=tot[i];k++){ //枚举每一组中的物品 if(t[i].tt[k].w<=j){ dp[j]=max(dp[j-t[i].tt[k].w]+t[i].tt[k].val,dp[j]); } } } } printf("%d",dp[m]); return 0; }
2、没有上司的舞会 洛谷P1352
今天下午陈枫继续讲dp课件呀qwq
然后快速讲完了以下全部内容(今天讲了好多好难的东西快速过了orz):
而讲了这么多,后面三段我说实话听懂的只有树形dp(可能是由于我做题了的原因)
这道题目就是一道经典的树形dp问题
废话不多说下面开始分析:
首先,什么是树形dp?
解释:
ROS评:废话
好的那我们还是要明白什么是树形dp呢?
我们在写01背包问题的时候我们会发现我们实质上是创建了一个一维(或二维)数组来模拟dp
而树形dp就是创建一个树来模拟dp的过程呀!!!
所以有没有豁然开朗???
如果你不知道树是什么,那ROS来解释一下(你总知道图是什么吧!!!没学过图论的我建议先学一下图论初步)
树是一种木本植物
莫得明白?
好吧那就拿你家的树举例子
(你家里还能种树?????)
就拿你家楼下的树举例子。
树有一个根,跟会生出枝干来,而枝干又会生出其他的枝干,而两个枝干之间的关系是无向的,即你只拿出一端时假设两个点左右的枝干一般粗你是看不出来有什么区别的即树是一个无向图。而无环就更好理解了:有环就是指一个枝干之后的若干子枝干又产生出了这条枝干,而这时绝对不可能的!!!所以无环就是非有环(正难则反的解释方式)。
所以信息学中的树就跟你家楼下树差不多。连叶子的定义都差不多:
定义一个点连边的条数为这个点的度数 最下层度数为 1 的点是叶子节点
森林就是一个多树集合,是不是很好理解!!!
所以现在开始讲正题:
(刚刚废话那么多干什么!!!!)
(还不是为了做铺垫?)
看到这道题目,我们能够分析出他是一道树上的问题。因为我们可以把那个所有其他人的顶级上司视为这棵树的根,则可以在树上写出转移方程:
void dp_(int x){ dp[x][0]=0; //如果x不参加舞会 dp[x][1]=a[x]; //如果x参加舞会 for(int i=0;i<son[x].size();i++){ int y= son[x][i]; dp_(y); dp[x][0]+=max(dp[y][1],dp[y][0]); dp[x][1]+=dp[y][0]; } return ; }
即从后往前进行树上dp的遍历从而更新答案。
每一个点都会有两种dp存在形势,即这个点所代表的人参加舞会,或不参加舞会。
而我们存每一个人的上司即开一个动态数组vector来储存每一个结点的上司。
所以我们大体的思路就有了呀QAQ
代码实现:
#include<iostream> #include<cstdio> #include<cmath> #include<vector> using namespace std; int n,a[6005]; bool vis[6005]; vector <int> son[6005]; int x,y; int root; int dp[6005][2]; void dp_(int x){ dp[x][0]=0; //如果x不参加舞会 dp[x][1]=a[x]; //如果x参加舞会 for(int i=0;i<son[x].size();i++){ int y= son[x][i]; dp_(y); dp[x][0]+=max(dp[y][1],dp[y][0]); dp[x][1]+=dp[y][0]; } return ; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } while(1){ scanf("%d%d",&x,&y); if(x==0&&y==0) break; son[y].push_back(x); vis[x]=true; } for(int i=1;i<=n;i++){ if(!vis[i]) { root=i; break; } } dp_(root); printf("%d",max(dp[root][1],dp[root][0])); return 0; }
明天就要模拟的呀好紧张好紧张QwQ
估计要爆零了
本来说做高二学长出的模拟题
后来高二学长又说陈枫出题
完了完了估计要挂了我还有好多东西没学懂orz
今天yxl学长给我讲了SPFA然后让我谢一篇有关SPFA的博客、、
一会儿条件允许的话写一下但可能是要咕了