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;
}

 

posted @ 2019-08-16 21:57  xqyxqy  阅读(144)  评论(0编辑  收藏  举报