15.2.27 DP练习

关键子工程(project.c/cpp/pas)

题目

在大型工程的施工前,我们把整个工程划分为若干个子工程,并把这些子工程编号为1、2、……、N;这样划分之后,子工程之间就会有一些依赖关系,即一些子工程必须在某些子工程完成之后才能施工。由于子工程之间有相互依赖关系,因此有两个任务需要我们去完成:首先,我们需要计算整个工程最少的完成时间;同时,由于一些不可预测的客观因素会使某些子工程延期,因此我们必须知道哪些子工程的延期会影响整个工程的延期,我们把有这种特征的子工程称为关键子工程,因此第二个任务就是找出所有的关键子工程,以便集中精力管理好这些子工程,尽量避免这些子工程延期,达到用最快的速度完成整个工程。为了便于编程,现在我们假设:

(1)根据预算,每一个子工程都有一个完成时间。
(2)子工程之间的依赖关系是:部分子工程必须在一些子工程完成之后才开工。
(3)只要满足子工程间的依赖关系,在任何时刻可以有任何多个子工程同时在施工,也既同时施工的子工程个数不受限制。
(4)整个工程的完成是指:所有子工程的完成。


例如,有五个子工程的工程规划表:

序号完成时间子工程1子工程2子工程3子工程4子工程5
子工程150000
子工程240000
子工程3120000
子工程471100
子工程521111

其中,表格中第I+1行J+2列的值如为0表示“子工程I”可以在“子工程J”没完成前施工,为1表示“子工程I”必须在“子工程J”完成后才能施工。上述工程最快完成时间为14天,其中子工程1、3、4、5为关键子工程。

又例如,有五个子工程的工程规划表:

序号完成时间子工程1子工程2子工程3子工程4子工程5
子工程150100
子工程240000
子工程3120010
子工程471100
子工程521111

上述的子工程划分不合理,因为无法安排子工程1,3,4的施工。


输入数据:

第1行为N,N是子工程的总个数,N≤200。
第2行为N个正整数,分别代表子工程1、2、……、N的完成时间。
第3行到N+2行,每行有N-1个0或1。其中的第I+2行的这些0,1,分别表示“子工程I”与子工程1、2、…、I-1、I+1、…N的依赖关系,(I=1、2、……、N)。每行数据之间均用一个空格分开。


输出数据:
如子工程划分不合理,则输出-1;
如子工程划分合理,则用两行输出:第1行为整个工程最少的完成时间。第2行为按由小到大顺序输出所有关键子工程的编号。


样例:
输入文件名:project. in
5
5 4 12 7 2
0 0 0 0
0 0 0 0
0 0 0 0
1 1 0 0
1 1 1 1


输出文件名:project. out
14
1 3 4 5


分析

由于题目中的工程有依赖关系,我们可以用拓扑排序来划分阶段。
根据题目给出来的图来构造拓扑序列储存在数组a中,如果存在环则输出“-1”.
然后使用动态规划来求出每个工程的最早完成时间,状态转移方程为: eet[a[i]]=max{eet[j]}+time[a[i]];map[a[i],j]=1 (1<=i<=n,1<=j<=n)
最短时间就是max(eet[a[i]]);
至于关键工程,有一个简单的道理:
最后完成的工程就是关键工程,影响关键工程的工程也是关键工程。

代码

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<set>
#define R0(i,n) for(int i=0;i<n;++i)
#define R1(i,n) for(int i=1;i<=n;++i)
#define cl(x,c) memset(x,c,sizeof x)
#define maxn 202
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
template <class T> inline void read(T&x){
    bool f=false;char ch;
    for (ch=getchar();ch<=32;ch=getchar());
    if(ch=='-')f=true,ch=getchar();
    for(x=0;ch>32;ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}
template <class T> inline void write(T x){
    if(x<0)  putchar('-'),x=-x;
    if(x<10) putchar(x+'0');
    else write(x/10),putchar(x%10+'0');
}
template <class T> inline void writeln(T x){
write(x),puts("");
}

int map[maxn][maxn],time[maxn],eet[maxn],into[maxn],a[maxn];
bool v[maxn];
int n,ans;
int main(){ 
freopen("project.in", "r", stdin);
freopen("project.out", "w", stdout);
read(n);
R1(i,n) read(time[i]);
cl(into,0);
R1(i,n)R1(j,n)if (i!=j)read(map[i][j]);
R1(i,n)R1(j,n)if(map[i][j]==1) ++into[i];
//读入
R1(i,n){
    int j=1;
    while(j <= n &&into[j] !=0)++j;
    if(j>n){writeln(-1); return 0;}
    a[i]=j;
    into[j]=-1;
    R1(k,n)if(map[k][j]==1)--into[k];
}
//拓扑排序
R1(i,n){
    R1(j,n)
        if(map[a[i]][j]==1)
            if(eet[j]>eet[a[i]])eet[a[i]]=eet[j];
    eet[a[i]]+=time[a[i]];
    ans=max(ans,eet[a[i]]);
}
//寻找最短时间
cl(v,0);
for(int i=n;i>=1;--i) {
    if(eet[a[i]]==ans)v[a[i]]=1;
    R1(j,n) if(map[j][a[i]]==1&&v[j]&&eet[a[i]]+time[j]==eet[j]) v[a[i]]=1;
    }
//寻找关键路径
writeln(ans);
R1(i,n)if(v[i])write(i),printf(" ");
return 0;
}

机器分配(machine.c/cpp/pas)

题目

某总公司拥有高效生产设备M台,准备分给下属的N个分公司。各分公司若获得这些设备,可以为总公司提供一定的盈利。
问:如何分配这M台设备才能使国家得到的盈利最大?求出最大盈利值。
分配原则:每个公司有权获得任意数目的设备,但总台数不得超过总设备数M。其中M<=100,N<=100。


输入数据:
第一行为两个整数M,N。接下来是一个N×M的矩阵,其中矩阵的第i行的第j列的数Aij表明第i个公司分配j台机器的盈利。所有数据之间用一个空格分隔。


输出数据:
只有一个数据,为总公司分配这M台设备所获得的最大盈利。


样例
输入文件名:machine.in
3 2
1 2 3
2 3 4

输出文件名:machine.out
4


分析

和01背包有些类似
设f[ i ][ j ]表示前i个公司分配j台机器的最大利润,则:
f[ i ][ j ]=max{f[ i-1 ][ k ]+_[ i ][ j-k ]} (1<=i<=n,1<=j<=m,0<=k<=j)
其中_[i,j]表示第i个公司分配j台机器的利润。

代码

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#define R0(i,n) for(register int i=0;i<n;++i)
#define R1(i,n) for(register int i=1;i<=n;++i)
#define cl(x,c) memset(x,c,sizeof x)
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
template <class T> inline void read(T&x){
    bool f = false; char ch;
    for (ch = getchar(); ch <= 32; ch = getchar());
    if (ch == '-') f = true, ch = getchar();
    for (x = 0; ch > 32; ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}
template <class T> inline void write(T x){
    if (x < 0) putchar('-'), x = -x;
    if (x < 10)
        putchar(x + '0');
    else
        write(x / 10), putchar(x % 10 + '0');
}
template <class T> inline void writeln(T x){
    write(x),puts("");
}
int m,n,_[110][110],f[110][110];
int main(){
    freopen("machine.in","r",stdin);
    freopen("machine.out","w",stdout);
    read(m),read(n);
    R1(i,n)R1(j,m)read(_[i][j]);
    R1(i,n)R1(j,m)R0(k,j+1)f[i][j]=max(f[i][j],f[i-1][k]+_[i][j-k]);
    writeln(f[n][m]);
    return 0;
}

编辑距离(edit.c/cpp/pas)

题目

字符串是数据结构和计算机语言里很重要的数据类型,在计算机语言中,对于字符串我们有很多的操作定义,因此我们可以对字符串进行很多复杂的运算和操作。实际上,所有复杂的字符串操作都是由字符串的基本操作组成。
例如,把子串a替换为子串b,就是用查找、删除和插入这三个基本操作实现的。
因此,在复杂字符串操作的编程中,为了提高程序中字符操作的速度,我们就应该用最少的基本操作完成复杂操作。
在这里,假设字符串的基本操作仅为:删除一个字符、插入一个字符和将一个字符修改成另一个字符这三种操作。
我们把进行了一次上述三种操作的任意一种操作称为进行了一步字符基本操作。
下面我们定义两个字符串的编辑距离:对于两个字符串a和b,通过上述的基本操作,我们可以把a变成b或b变成a;那么,把字符串a变成字符串b需要的最少基本字符操作步数称为字符串a和字符串b的编辑距离。
例如,如a=“ABC”,b=“CBCD”,则a与b的编辑距离为2。
你的任务就是:编一个最快的程序来计算任意两个字符串的编辑距离。


输入数据:
第1行为字符串a;第2行为字符串b。注:字符串的长度不大于1000,字符串中的字符全为大写字母。


输出数据:
编辑距离。


样例
输入文件名:edit.in
ABC
CBCD


输入文件名:edit.out
2


分析

这个一看就知道是DP,,但懒惰的毛病又犯当时也没怎么深入思考
设 _ [ i ][ j ]为使a的前i个变成和b的前j个相同的编辑距离
别问我下划线是什么鬼

因为当n为空串时, _ [ m ][ n ] = m
当m为空串时,_ [ m ][ n ] = n;
得到边界条件:
_ [ i ][ 0 ] = i
_ [ 0 ][ j ] = j
然后DP
不改 _ [ i ][ j ]=_ [ i-1 ][ j-1 ] ( a[ i-1 ]==b[ j-1 ] )

改的话就是喜闻乐见的3选1
插入 _ [ i ][ j-1 ]+1
删除 _ [ i-1 ][ j ]+1
修改 _ [ i-1 ][ j-1 ]+1
找最小的就好了

代码

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#define R0(i,n) for(register int i=0;i<n;++i)
#define R1(i,n) for(register int i=1;i<=n;++i)
#define cl(x,c) memset(x,c,sizeof x)
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
template <class T> inline void read(T&x){
    bool f = false; char ch;
    for (ch = getchar(); ch <= 32; ch = getchar());
    if (ch == '-') f = true, ch = getchar();
    for (x = 0; ch > 32; ch = getchar()) x = x * 10 + ch - '0';
    if (f) x = -x;
}
template <class T> inline void write(T x){
    if (x < 0) putchar('-'), x = -x;
    if (x < 10)
        putchar(x + '0');
    else
        write(x / 10), putchar(x % 10 + '0');
}
template <class T> inline void writeln(T x){
    write(x),puts("");
}
template <class T> inline T mmn(T x,T y,T z){
    return min(x,min(y,z));
}
int _[1003][1003];
char a[1003],b[1003];
int main(){
    freopen("edit.in","r",stdin);
    freopen("edit.out","w",stdout);
    scanf("%s%s",&a,&b);
    int m=strlen(a),n=strlen(b);
    R1(i,m)_[i][0]=i;
    R1(i,n)_[0][i]=i;
    R1(i,m)R1(j,n)
        if(a[i-1]==b[j-1])_[i][j]= _[i-1][j-1];
        else _[i][j]= mmn(_[i-1][j],_[i][j-1],_[i-1][j-1]) +1;
    writeln(_[m][n]);
    return 0;
}

硬币找零coin.c/cpp/pas

题目

在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资。
我们应该注意到,人民币的硬币系统是100,50,20,10,5,2,1,0.5,0.2,0.1,0.05,0.02,0.01元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数。
但不幸的是:我们可能没有这样一种好的硬币系统,因此用贪心算法不能求出最少的硬币数,甚至有些金钱总数还不能用这些硬币找零。
例如,如果硬币系统是40,30,25元,那么37元就不能用这些硬币找零;
95元的最少找零硬币数是3。
又如,硬币系统是10,7,5,1元,那么12元用贪心法得到的硬币数为3,而最少硬币数是2。
你的任务就是:对于任意的硬币系统和一个金钱数,请你编程求出最少的找零硬币数;
如果不能用这些硬币找零,请给出一种找零方法,使剩下的钱最少。


输入数据:
第1行,为N和T,其中1≤N≤500为硬币系统中不同硬币数;1≤T≤10000为需要用硬币找零的总数。
第2行为N个数值不大于65535的正整数,它们是硬币系统中各硬币的面值。


输出数据:
如T能被硬币系统中的硬币找零,请输出最少的找零硬币数。
如T不能被硬币系统中的硬币找零,请输出剩下钱数最少的找零方案中的所需的最少硬币数。


样例
输入文件名:coin.in
4 12
10 7 5 1

输出文件名:coin.out
2


分析

完全背包
f [ j ]表示找i的钱最小数量
f [ j ]=min( f[ j ] , f[ j-a[i] ]+1)
算完从后往前找f[ j ] != INF的就是答案
↑找零剩下钱数最少自然就是找的钱最多了
一开始把完全背包转成01背包做的,又T又W

代码

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<queue>
#include<set>
#define R0(i,n) for(int i=0;i<n;++i)
#define R1(i,n) for(int i=1;i<=n;++i)
#define cl(x,c) memset(x,c,sizeof x)
#define maxn 505
#define maxt 100005
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
template <class T> inline void read(T&x){
    bool f=false;char ch;
    for (ch=getchar();ch<=32;ch=getchar());
    if(ch=='-')f=true,ch=getchar();
    for(x=0;ch>32;ch=getchar()) x=x*10+ch-'0';
    if(f) x=-x;
}
template <class T> inline void write(T x){
    if(x<0)  putchar('-'),x=-x;
    if(x<10) putchar(x+'0');
    else write(x/10),putchar(x%10+'0');
}
template <class T> inline void writeln(T x){
write(x),puts("");
}
int f[maxt],a[maxn],n,t;
int main(){
    read(n),read(t);
    R1(i,n)read(a[i]);
    cl(f,0x3f);
    f[0]=0;
    R1(i,n)for(int j=a[i];j<=t;++j)f[j]=min(f[j],f[j-a[i]]+1);
    if(f[t]!=INF) writeln(f[t]);
    else for(int i=t;i>=0;--i) if(f[t]!=INF) writeln(f[t]);
    return 0;
}

总结

1

? : 貌似不能随意代替if,t3把if换掉就W了

STL的unique并不真正把重复的元素删除,是把重复的元素移到后面去了。
所以说set大法好

2

DP是硬伤,一直处于一种找不到感觉的状态。
之前就没怎么学扎实,现在往后学又导致了遗忘,就像“黑瞎子掰棒子”一样。
这样下去只会学的越多会的越少。
准备来一波复习吧。

仰天大笑出门去,无人知是荔枝来
また あした~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值