DP套题练习1
前言:练习①不难,但也有注意的地方.
Q1: 给定AOE网络工程图,求完成时间及其中的关键工程.
S1:先拓扑排序[记得用队列,O(n)的复杂度],确定DP的顺序(后效性).DP方程显然为:f[ to ] = max( f[ to ] , f[ x ] + val[ to ] ).求关键工程则逆推DP状态的转移过程.
细节:注意最后可能有多条路劲同时完成,要注意处理.
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> #include<queue> using namespace std; #define e exit(0) #define R register int n,cnt,cnt1,deep,ans,id,lenth,val[210],head[210],head1[210],rd[210],dl[210],cd[210],s[210][210],f[210],ask[210],vis[210]; struct bian{ int to,next; }len[210*210]; struct bian1{ int to,next; }len1[210*210]; void add(int from,int to) { len[++cnt].to=to; len[cnt].next=head[from]; head[from]=cnt; } void add1(int from,int to) { len1[++cnt1].to=to; len1[cnt1].next=head1[from]; head1[from]=cnt1; } void tpsort() { queue<int> q; for(R int i=1;i<=n;++i) if(!rd[i]){ q.push(i); dl[++deep]=i; f[i]=val[i]; } while(q.size()){ int now=q.front(); q.pop(); for(R int k=head[now];k;k=len[k].next){ int to=len[k].to; --rd[to]; if(rd[to]==0){ dl[++deep]=to; q.push(to); } } } if(deep!=n){ printf("-1"); exit(0); } } void dfs(int x) { for(R int k=head1[x];k;k=len1[k].next){ int to=len1[k].to; if(f[x]==f[to]+val[x]){ if(!vis[to]){ ask[++lenth]=to; vis[to]=1; dfs(to); } } } } int main() { freopen("project.in","r",stdin); freopen("project.out","w",stdout); scanf("%d",&n); for(R int i=1;i<=n;++i) scanf("%d",&val[i]); for(R int i=1;i<=n;++i) for(R int j=1;j<=n;++j) if(i!=j) s[i][++s[i][0]]=j; for(R int i=1;i<=n;++i){ int to=i; for(R int j=1;j<=n-1;++j){ int from=s[i][j],num; scanf("%d",&num); if(num==0) continue; add(from,to),add1(to,from); ++rd[to]; ++cd[from]; } } tpsort(); for(R int i=1;i<=deep;++i){ int now=dl[i]; for(R int k=head[now];k;k=len[k].next){ int to=len[k].to; f[to]=max(f[to],f[now]+val[to]); } } for(R int i=1;i<=n;++i) if(!cd[i]){ if(f[i]>ans) ans=f[i]; } for(R int i=1;i<=n;++i) if(!cd[i]&&!vis[i]){ if(f[i]==ans) ask[++lenth]=i,vis[i]=1; } printf("%d\n",ans); for(R int i=1;i<=lenth;++i) dfs(ask[i]); sort(ask+1,ask+1+lenth); for(R int i=1;i<=lenth;++i) printf("%d ",ask[i]); return 0; }
Q2:某总公司拥有高效生产设备 M 台,准备分给下属的 N 个分公司.各分公司若获得这些设备,可以为总公司提供一定的盈利.问:如何分配这 M 台设备才能使国家得到的盈利最大?求出最大盈利值.分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数 M。其中M<=100,N<=100.
S2:显然的线性DP,可以借用背包的思想,设f[ i ][ j ]表示我们前 i 个公司共选了j台设配获得的最大收益.枚举当前选入设备总台数k与上一次总台数k,便有了转移方程f[ i ][ j ] = max(f[ i ][ j ],f[ i - 1][ k ]+val[ j -k] ).注意初始化: f[ 1 ][ j ]=val[1][ j ].
#include<iostream> #include<cstring> #include<cstdio> using namespace std; #define e exit(0) #define R register int m,n,ans,f[110][110],val[110][110]; int main() { freopen("machine.in","r",stdin); freopen("machine.out","w",stdout); scanf("%d%d",&m,&n); for(R int i=1;i<=n;++i) for(R int j=1;j<=m;++j) scanf("%d",&val[i][j]); for(R int j=1;j<=m;++j) f[1][j]=val[1][j]; for(R int k=1;k<=n;++k) for(R int i=0;i<=m;++i) for(R int j=0;j<=i;++j) f[k][i]=max(f[k][i],f[k-1][j]+val[k][i-j]); for(R int j=0;j<=m;++j) ans=max(ans,f[n][j]); printf("%d",ans); return 0; }
Q3:给定字符串S,T.给定操作替换,插入,删除,每进行其中之一为一次操作,问最少多少次操作S变成T.
S3:对于两个字符串之间的DP,我们一般会设f[ i ][ j ]表示字符串S的前 i 位与T的前 j 位进行xxx操作,维护max/min值.这题我们顺这思路想,设f[ i ][ j ]为将字符串S的前 i 位变成T的前 j 位最少操作数.如果s[ i ]==t[ j ],f[ i ][ j ]=f[ i-1][ j-1].如果s[ i ]!=t[ j ],则进行三种操作.①f[ i ][ j ]=min{f[ i -1][ j-1]+1},表示直接替换.②f[ i ][ j ]=min{f[ i ][ j-1]+1},表示将S的前i位变成T的前j-1位,再加一位.③f[ i ][ j ]=min{f[ i -1][ j ]+1},表示将S的前i-1位变成T的前j位,再删一位.
细节:注意初始化,f[ 0 ][ j ] = j, 0 位变成 j 位显然要用 j 次操作,f[ i ][ 0 ] = i 则同理.
#include<iostream> #include<cstring> #include<cstdio> using namespace std; #define e exit(0) #define R register char s[1010],t[1010]; int lens,lent,f[1010][1010]; int main() { freopen("edit.in","r",stdin); freopen("edit.out","w",stdout); scanf("%s",s+1),scanf("%s",t+1); lens=strlen(s+1),lent=strlen(t+1); memset(f,0x7f,sizeof(f)); f[0][0]=0; for(R int i=1;i<=lens;++i) f[i][0]=i; for(R int j=1;j<=lent;++j) f[0][j]=j; for(R int i=1;i<=lens;++i) for(R int j=1;j<=lent;++j){ if(s[i]==t[j]) f[i][j]=f[i-1][j-1]; else if(s[i]!=t[j]){ f[i][j]=min(f[i][j],f[i-1][j-1]+1); f[i][j]=min(f[i][j],f[i][j-1]+1); f[i][j]=min(f[i][j],f[i-1][j]+1); } } printf("%d",f[lens][lent]); return 0; }
Q4:给n个硬币面值,求对于面值v,我们最少用多少枚硬币表示,若不能则找出能表示的次小面值v',求其硬币表示数,硬币可重复使用.
S4:显然完全背包的模板,我们改下定义即可,f[ i ]表示面值i能被表示的最少硬币数,f[ i ] = min{f[ i -cost ]+1}.注意初始化原始面值的f数组都为1.
细节:当f[i-cost]==0,此状态不合法,舍去.
#include<iostream> #include<cstring> #include<cstdio> using namespace std; #define R register int n,v,val[60],f[100010]; int main() { freopen("coin.in","r",stdin); freopen("coin.out","w",stdout); scanf("%d%d",&n,&v); memset(f,0x7f,sizeof(f)); for(R int i=1;i<=n;++i){ scanf("%d",&val[i]); f[val[i]]=1; } for(R int i=1;i<=n;++i) for(R int j=val[i];j<=v;++j){ if(f[j-val[i]]==0) continue; f[j]=min(f[j],f[j-val[i]]+1); } if(f[v]!=2139062143) printf("%d",f[v]); else if(f[v]==2139062143){ for(R int i=v-1;i>=1;--i) if(f[v]!=2139062143){ printf("%d",f[v]); break; } } return 0; }