树形dp:
题意:有N个职员,编号1~N,每个职员都有一个快乐指数H[ i ]。他们的关系就像是一个以校长为根节点的树,上下级之间构成父子节点关系。现在他们要举办一场宴会,如果直接上级参加了,则直接下属不会参加。所以问你,怎样才能使得宴会上所有人的快乐指数之和最大。
思路:一道树形dp,以树的节点编号为dp的阶段,同时宴会当中某个人的状态只有两种,参加或不参加,所以用dp[ i ][ 0 ]表示 i 号节点不参加,dp[ i ][ 1 ]表示i号节点参加。树形dp,状态的转移,以子节点为前一阶段,父节点的当前阶段由上一阶段子节点推出。所以要先求出叶子节点的dp值(边界值),这就要采用递归的方式求解。状态转移方程就不写了
#include<bits/stdc++.h> using namespace std; int n; int h[6600]; vector<int> tr[6600]; int f[6600][2],v[6600]; void dp(int root) { f[root][0]=0; f[root][1]=h[root]; int len=(int)tr[root].size(); for(int j=0;j<len;j++) { int y=tr[root][j]; dp(y); f[root][0]+=max(f[y][0],f[y][1]); f[root][1]+=f[y][0]; } } int main() { cin>>n; for(int i=1;i<=n;i++) scanf("%d",&h[i]); int x,y; for(int i=1;i<=n;i++) { cin>>x>>y; v[x]=1; tr[y].push_back(x); } int root; for(int i=1;i<=n;i++) { if(!v[i]){root=i;break;} } dp(root); cout<<max(f[root][0],f[root][1])<<"\n"; return 0; }
环形dp:
环形dp求解方法有两种:1.先把环形dp当成线性dp求解。然后注意,线性dp变为环形dp后,多了哪些特殊情况,把这些特殊情况处理掉。所以这种方法意思就是,环形dp=线性dp+线性变环形后的多出来的特殊情况。
题意:某个星球上一天有N个小时,0点到1点为第一个小时,1点到2点为第二个小时,以此类推。并且再第 i 个小时睡觉能恢复 Ui 小时的体力。现在又一头牛每天要睡 B 个小时。但这 B 个小时不一定连续,也就是说其休息时间可能是几个小段。并且每一个小段第一个小时不能恢复体力。现在让你给这头安排一个休息计划,使得它每天恢复的体力最多。
思路:先把它按照线性来处理,就是不理会前一天的第N个小时和后一天的第1个小时是连续的。这样由于时间是增长的,选取时间作为dp的阶段,则当前牛只有两个状态要么睡觉,要么醒着。要想要实现最优子结构的状态转移,同时还要知道它已经休息了多长时间。所以用dp[ i ][ j ][ 0 ]表示前 i 小时已经休息了 j 个小时,并且当前是醒着的状态时恢复的最大体力。dp[ i ][ j ][ 1 ]表示当前前 i 个小时休息了 j 个小时,并且当前是睡觉状态恢复的最大体力。 状态转移方程不写了。这个时候dp的边界条件是 dp[ 1 ][ 0 ][ 0 ]=dp[ 1 ][ 1 ][ 1 ]=0; 而相比于环形,环形仅仅多了一种极端情况 dp[ 1 ][ 1 ][ 1 ]=U[ 1 ],所以环形情况下只要令初始dp值为它,再跑一次线性dp就可以了。
#include<iostream> #include<stdio.h> #include<cmath> #include<cstring> using namespace std; const int inf=0x3f3f3f3f; typedef long long ll; ll U[4000]; int N,B; ll ans1,ans2; ll dp[4000][2]; /*struct node { int id; int state; }D[4000][4000][2],D2[4000][4000][2]; void update1(int cur,int i,int j) { if(dp[i-1][j][0]>dp[i-1][j][1]) { dp[i][j][0]=dp[i-1][j][0]; if(cur==1) { D[i][j][0].id=i-1;D[i][j][0].state=0; } else { D2[i][j][0].id=i-1;D2[i][j][0].state=0; } } else { dp[i][j][1]=dp[i-1][j][1]; if(cur==1) { D[i][j][0].id=i-1;D[i][j][0].state=1; } else { D2[i][j][0].id=i-1;D2[i][j][0].state=1; } } } void update2(int cur,int i,int j) { //dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j-1][1]+U[i]); if(dp[i-1][j-1][0]>dp[i-1][j-1][1]+U[i]) { dp[i][j][1]=dp[i-1][j-1][0]; if(cur==1) { D[i][j][1].id=i-1; D[i][j][1].state=0; } else { D2[i][j][1].id=i-1; D2[i][j][1].state=0; } } else { dp[i][j][1]=dp[i-1][j-1][1]+U[i]; if(cur==1) { D[i][j][1].id=i-1; D[i][j][1].state=1; } else { D2[i][j][1].id=i-1; D2[i][j][1].state=1; } } } */ int main() { cin>>N>>B; for(int i=1;i<=N;i++) { cin>>U[i]; } memset(dp,-inf,sizeof dp); dp[0][0]=0; dp[1][1]=0; for(int i=2;i<=N;i++) for(int j=i;j>=0;j--) { dp[j][0]=max(dp[j][0],dp[j][1]); if(j-1>=0) dp[j][1]=max(dp[j-1][0],dp[j-1][1]+U[i]); } ans1=max(dp[B][0],dp[B][1]); memset(dp,-inf,sizeof dp); dp[1][1]=U[1]; for(int i=2;i<=N;i++) for(int j=i;j>=0;j--) { dp[j][0]=max(dp[j][0],dp[j][1]); if(j-1>=0) dp[j][1]=max(dp[j-1][0],dp[j-1][1]+U[i]); } ans2=dp[B][1]; cout<<max(ans1,ans2)<<"\n"; return 0; }
第二种处理方法:复制一遍要处理的序列加到第一条序列末尾。
状态 压缩dp:有的时候不仅需要由上一阶段的最优值来推下一阶段的最优值,并且下一阶段的某些状态与上一阶段的某些状态有依赖关系,所以这个时候我们不仅要保存上一阶段的最优值,而且还需要上一阶段的状态。这个时候我们把状态压缩成一个十进制的整数,十进制转化为二进制其 0 1的分布情况就代表了状态分布。
题意:给你一个N*M的棋盘,现在让你把它分割成若干个1*2的长方形。有多少种分割方案。
思路:行再线性递增,选取行作为dp的阶段,其中的每一个小格子只有两种情况要么是属于横着放的1*2的长方形,要么是属于竖着放的1*2的小格子,当它属于竖着放的1*2的小格子的时候。它对后面一行就会产生影响,后一行必定有一个格子要接在它的后面。而一行刚好有M个格子,我们就可以用一个M位的二进制的每一位是0还是1来表示当前格子是竖着的还是横着的。则 状态定义 为 dp[ i ][ j ]表示第 i 行,当前格子分布情况为 j ;
最短路:
题意:当前有M个任务,每个任务给定开始时间,和结束时间。小明按照一定的规则来做任务,即当前时刻若小明空闲,并且有任务开始,则分配给小明。若当前有多个任务,则选择其中一个任务。问怎样安排任务给小明,则小明的空闲时间最多。
思路:原本是dp,但是dp不会,题解也看不懂。把每个任务的开始时刻作为父节点,其任务结束时刻作为子节点,把之间相隔的时间作为边权值,但是这样的话都得到的图是很多个分裂开来的树。则把出度为0的节点即,任务结束的时刻与它的下一时刻的时间节点相连,但由于中间没有任务,所以把边权值设为0,这样就得到了一个带权的有环图,再环上跑一边dijkstra,得到最短路,最后再用总时间减去最短路就得到最多休息时间了。
但是这道题一开始把优先队列,写成一般队列了,还一直没找出错。。。
#include<bits/stdc++.h> using namespace std; const int maxn=20000; priority_queue< pair<int,int> > q; int n,m; int cnt=0; int head[maxn]; struct Edge{ int next; int to; int w; }edge[maxn]; void add(int u,int v,int w) { edge[++cnt].next=head[u]; edge[cnt].to=v; edge[cnt].w=w; head[u]=cnt; } int dis[maxn]; int vis[maxn]; void dijkstra() { memset(dis,0x3f,sizeof dis); dis[1]=0; q.push(make_pair(0,1)); //q.push(1); int now; while(!q.empty()) { now=q.top().second; q.pop(); if(vis[now]) continue; //vis[now]=false; vis[now]=1; for(int i=head[now];i;i=edge[i].next) { if(dis[edge[i].to]>dis[now]+edge[i].w) { dis[edge[i].to]=dis[now]+edge[i].w; q.push(make_pair(-dis[edge[i].to],edge[i].to)); //vis[edge[i].to]=1; } } } } int main() { scanf("%d%d",&n,&m); int x,y; for(int i=1;i<=m;i++) { cin>>x>>y; add(x,x+y,y); } for(int i=1;i<=n;i++) { if(!head[i]) { add(i,i+1,0); } } dijkstra(); cout<<n-dis[n+1]; return 0; }
dpP1140 相似基因
题意:给你一个两个DNA序列,问他们的最大相似度是多少。
思路:这道题绕的很,首先由两个序列的最长上升子序列,可以想到 维度肯定是两个 。并且 dp[ i ][ j ]表示第一个DNA序列前 i 个与 第二个DNA 序列前 j 个的最大相似度。并且其肯定是由 dp[ i-1 ][ j ] 和dp[ i-1 ][ j-1 ],dp[ i-1 ][ j-1 ]推出来的。可是这个题把空白碱基对加上,让人迷糊的很。所以举个例子 dp[ 4 ][ 3 ],若其由 dp[ 4 ][ 2 ]推出,则现在第二个DNA序列其第3个基因只能和第一个DNA序列的空白基因相配,,,若其由dp[ 3 ][ 3 ]推出,则其第一个DNA序列的第四个基因只能与第二个DNA序列的空白基因相配,若由dp[ 3 ][ 2] 推出,其第一个DAN序列第四个基因和第二个DNA序列的第3个基因直接相配。
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; int dp[150][150]; int x,y; string s1,s2; int char_num(char r) { if(r=='A') return 1; if(r=='C') return 2; if(r=='G') return 3; if(r=='T') return 4; } int main() { int v[6][6]={ {0,0,0,0,0,0}, {0,5,-1,-2,-1,-3}, {0,-1,5,-3,-2,-4}, {0,-2,-3,5,-2,-2}, {0,-1,-2,-2,5,-1}, {0,-3,-4,-2,-1,0} }; cin>>x>>s1; cin>>y>>s2; memset(dp,-inf,sizeof dp); dp[0][0]=0; for(int i=1;i<=x;i++) { //cout<<"---"<<s1[i-1]<<"\n"; // cout<<"+++"<<char_num(s1[i-1])<<"\n"; dp[i][0]=dp[i-1][0]+v[char_num(s1[i-1])][5]; } for(int i=1;i<=y;i++) dp[0][i]=dp[0][i-1]+v[5][char_num(s2[i-1])]; for(int i=1;i<=x;i++) for(int j=1;j<=y;j++) { dp[i][j]=max(dp[i][j],dp[i][j-1]+v[5][char_num(s2[j-1])]); dp[i][j]=max(dp[i][j],dp[i-1][j]+v[char_num(s1[i-1])][5]); dp[i][j]=max(dp[i][j],dp[i-1][j-1]+v[char_num(s1[i-1])][char_num(s2[j-1])]); } cout<<dp[x][y]; return 0; }
先贪心在dp的题
有的题目(一般会给出n个物品), 当前这个物品对最优值的贡献与他和其他物品的相对顺序有关,也就是说它的贡献度不是一个常量。这个时候一般就要把这些物品先按优先级排一个序。为什么普通的背包问题不用排序,因为每个物品对最优值的贡献度是唯一的,因为它们的价值是唯一的。
那么这种优先级该如何确定啦,以下面两道题为例子,其实方法都一样,一开始只考虑相邻两项
1.
题意:现有M个饼干,分给N个孩子。每个孩子有一个贪婪度,g[ i ]。对于每个孩子,如果有a[ i ]个孩子比他拿到的饼干多,那么他就会产生a[ i ]*b[ i ]的怨气。现在问你如何安排才能使得怨气总和最小。
题意:每个孩子对总怨气的贡献与他和别的孩子的相对顺序有关,所以先确定优先级,考虑相邻孩子a和孩子b,且怨气:
a<b.则如果孩子a排在前面其对总的怨气贡献度为 0*a+1* b=b。孩子b排在前面其对总的怨气的贡献度为 0*b+1*a=a。很明显
a<b.所以应当把怨气大的排在前面。这样优先度,就确定了。
2.题意:从0开始计时,一直到T时刻结束。现在给你n种食材,每种食材都有三个属性,ai,bi,ci, ci为用这种食材做饭所要花的时间。
每种食材对美味度的贡献度为ai-t*bi。问你在T时刻结束时,总的美味度最大可能是多少。
思路:每种食材对总美味度的贡献依旧不是一个常数,这与他和别的食材的相对顺序有关,所以先考虑相邻两项.先做a[ 1 ]后做 a[ 2 ]对美味度的贡献度,先做a[ 2 ]在做 a[ 1 ]对美味度的贡献度。
a[ 1 ]-(p+c[ 1 ])*b[ 1 ]+a[ 2 ]-(p+c[ 1 ]+c[ 2 ])b[ 2 ] 与a[ 2 ]-(p+c[ 2 ])*b[ 2 ]+a[ 1 ]-(p+c[ 1 ]+c[ 2])*b[ 1 ]进行大小比较,就能得出一式大于2式子的条件是c[1]*b[2]<c[2]*b[1].这样就把乘积小的排在前面。
剩下的就01背包一下
#include<bits/stdc++.h> using namespace std; typedef long long ll; int T,n; ll dp[110000]; struct node{ ll a,b,c=0; }dot[100]; bool cmp(node x,node y) { return x.c*y.b<y.c*x.b; } int main() { scanf("%d%d",&T,&n); for(int i=1;i<=n;i++) scanf("%d",&dot[i].a); for(int i=1;i<=n;i++) scanf("%d",&dot[i].b); for(int i=1;i<=n;i++) scanf("%d",&dot[i].c); sort(dot+1,dot+1+n,cmp); for(int i=1;i<=n;i++) { for(int j=T;j>=dot[i].c;j--) dp[j]=max(dp[j],dp[j-dot[i].c]+dot[i].a-dot[i].b*j); } ll ans=0; for(int i=0;i<=T;i++) ans=max(ans,dp[i]); cout<<ans; return 0; }