树形dp:
思路:一道树形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的长方形。有多少种分割方案。
最短路:
题意:当前有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序列,问他们的最大相似度是多少。
#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; }