20150227题解

来源:互联网 发布:程序员常用字体 编辑:程序博客网 时间:2024/06/04 00:31

关键子工程(project)
题目:
在大型工程的施工前,我们把整个工程划分为若干个子工程,并把这些子工程编号为1、2、……、N;这样划分之后,子工程之间就会有一些依赖关系,即一些子工程必须在某些子工程完成之后才能施工。由于子工程之间有相互依赖关系,因此有两个任务需要我们去完成:首先,我们需要计算整个工程最少的完成时间;同时,由于一些不可预测的客观因素会使某些子工程延期,因此我们必须知道哪些子工程的延期会影响整个工程的延期,我们把有这种特征的子工程称为关键子工程,因此第二个任务就是找出所有的关键子工程,以便集中精力管理好这些子工程,尽量避免这些子工程延期,达到用最快的速度完成整个工程。为了便于编程,现在我们假设:
(1)根据预算,每一个子工程都有一个完成时间。
(2)子工程之间的依赖关系是:部分子工程必须在一些子工程完成之后才开工。
(3)只要满足子工程间的依赖关系,在任何时刻可以有任何多个子工程同时在施工,也既同时施工的子工程个数不受限制。
(4)整个工程的完成是指:所有子工程的完成。
例如,有五个子工程的工程规划表:
序号 完成时间 子工程1 子工程2 子工程3 子工程4 子工程5
子工程1 5 0 0 0 0
子工程2 4 0 0 0 0
子工程3 12 0 0 0 0
子工程4 7 1 1 0 0
子工程5 2 1 1 1 1
其中,表格中第I+1行J+2列的值如为0表示“子工程I”可以在“子工程J”没完成前施工,为1表示“子工程I”必须在“子工程J”完成后才能施工。上述工程最快完成时间为14天,其中子工程1、3、4、5为关键子工程。

又例如,有五个子工程的工程规划表:
序号 完成时间 子工程1 子工程2 子工程3 子工程4 子工程5
子工程1 5 0 1 0 0
子工程2 4 0 0 0 0
子工程3 12 0 0 1 0
子工程4 7 1 1 0 0
子工程5 2 1 1 1 1
上述的子工程划分不合理,因为无法安排子工程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

题解:
本题是一个经典的dp题,题目大意是完成三个任务:
1、给定一个拓扑图,判断是否有环;
2、给定一个无环拓扑图,求总最早完成时间;
3、求无环拓扑图中是否有子工程可以延迟。
N<=200的规模搜索至少是很勉强,但更快的是记忆化搜索。
对第一个任务,可以简单地拓扑排序,如果有环输出“-1”并退出,这样至少可得10分;
第二个任务则是记忆化搜索,(本质上还是dp)若第x个子工程依赖别的子工程i,则递归计算i;若i已被算出,直接返回答案;x的最短完成时间即为它所依赖的所有子工程中完成时间最长的加上x本身的完成时间;
第三个任务依然是记忆化搜索。考虑第x个子工程,它能被延迟当且仅当它的最晚完成时间小于等于全部{【依赖x的所有子工程】在不影响总最早完成时间的情况下的最晚开始时间}的最小值。(dp方向与第二个任务相反)
初始化最晚完成时间为总最早完成时间;(要从后往前推)
若有工程i依赖x,则递归计算i;若i已被算出,直接返回答案;x的最晚完成时间即为所有依赖x的子工程最晚开始时间的最小值。(该值等于min{所有依赖x的子工程i最晚完成时间减去i工程所需要的时间})
若某工程的最早完成时间与最晚完成时间相等,则不能被延迟,应输出。
CODE

#include<cstdio>#include<algorithm>#include<queue>#include<cstring>using namespace std;int n, map[201][201], t[201], sumt;          int imp[201], top;      int ealy[201], lst[201];queue <int> Q;inline void init() {        scanf("%d", &n);    for(int i = 1; i <= n; ++i) scanf("%d", &t[i]);    for(int i = 1; i <= n; ++i)         for(int j = 1; j <= n; ++j)            if(i == j) map[i][j] = 0;            else {                scanf("%d", &map[i][j]);                if(map[i][j]) ++map[i][0];            }}inline bool topo() {        int din[201], sigma = n;    bool exist[201];    memset(exist, true, sizeof(exist));    for(int i = 1; i <= n; ++i) din[i] = map[i][0];    while(sigma--) {        bool b = false;        for(int i = 1; i <= n; ++i) if(exist[i])            if(din[i] == 0) {                b = true;                exist[i] = false;                for(int j = 1; j <= n; ++j)                     if(map[j][i]) --din[j];                break;            }        if(!b) return false;    }    return true;}void remsearch1(int x) {        if(ealy[x]) return;    for(int i = 1; i <= n; ++i) if(map[x][i]) {        remsearch1(i);        ealy[x] = max(ealy[x], ealy[i]);    }    ealy[x] += t[x];}void remsearch2(int x) {        if(lst[x] < sumt) return;    for(int i = 1; i <= n; ++i) if(map[i][x]) {        remsearch2(i);        lst[x] = min(lst[x], lst[i]-t[i]);    }}inline void work() {        for(int i = 1; i <= n; ++i) remsearch1(i);    for(int i = 1; i <= n; ++i) sumt = max(sumt, ealy[i]);    for(int i = 1; i <= n; ++i) lst[i] = sumt;    for(int i = 1; i <= n; ++i) remsearch2(i);    for(int i = 1; i <= n; ++i) if(lst[i] == ealy[i])        imp[++top] = i;}inline void print() {       printf("%d\n", sumt);    for(int i = 1; i <= top; ++i)         printf("%d ", imp[i]);}int main() {    freopen("project.in", "r", stdin);    freopen("project.out", "w", stdout);    init();    if(!topo()) {               puts("-1");        return 0;    }    work();    print();    return 0;}

机器分配(machine)
题目:
某总公司拥有高效生产设备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

题解:
一开始把题目中的j台看成了第j台,心想不就是费用流吗?结果写了一半以后,看了看样例,发现自己看错题了。但我仍不思悔改,继续YY网络流模型,最后YY出一个有流量下界的费用流模型,不会了,终于死心了。(或许能做)
本题与01背包很相似,用w【i】【j】记录第i家公司分配j台机器所得的盈利,用f【i】【v】表示给前i个公司一定量机器还剩余v台机器所得的最大盈利,显然有:
f【i】【v】 = max{f【i】【v】, f【i】【v-j】+w【i】【j】}
其中i从1到n,v从1到m,j从1到v;
最终答案为max{f【n】【0->m】}
可用滚动数组优化空间,见代码。
有优化时间复杂度的余地。(还不会)
时间复杂度O(n*m^2);空间复杂度O(n*m);
CODE

#include<cstdio>#include<algorithm>using namespace std;int f[110], map[110][110], m, n, ans; int main() {    freopen("machine.in", "r", stdin);    freopen("machine.out", "w", stdout);    scanf("%d%d", &m, &n);    for(int i = 1; i <= n; ++i)         for(int j = 1; j <= m; ++j)            scanf("%d", &map[i][j]);    for(int i = 1; i <= n; ++i)         for(int v = m; v >= 1; --v)            for(int j = 1; j <= v; ++j)                f[v] = max(f[v], f[v-j] +map[i][j]);     for(int i = 0; i <= m; ++i) ans = max(f[i], ans);    printf("%d", ans);    return 0;}

编辑距离(edit)
题目:
字符串是数据结构和计算机语言里很重要的数据类型,在计算机语言中,对于字符串我们有很多的操作定义,因此我们可以对字符串进行很多复杂的运算和操作。实际上,所有复杂的字符串操作都是由字符串的基本操作组成。例如,把子串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

题解:
首先要明确:在一个串中插入等价于在另一个串中删除;
1000的规模顶多是O(n^2)的算法,又是字符串匹配,首选dp;
设f【m】【n】表示长为m的串与长为n的串的编辑距离;
本题中m = length(a); n = length(b);
一开始就分析一般情况肯定会遇到困难,(神犇们请忽略这句话)由浅入深嘛:
当n为空串时,f【m】【n】 = m;
当m为空串时,f【m】【n】 = n;
于是我们得到了边界条件:
f【i】【0】 = i; f【0】【j】 = j;(i = 1->m, j = 1->n)
考虑a【i】与b【j】,若二者相等,显然f【i】【j】 = f【i-1】【j-1】;
若不等,有三种选择:
1、删a【i】,f【i】【j】 = f【i-1】【j】+1;
2、删b【i】,f【i】【j】 = f【i】【j-1】+1;
3、a【i】替换为b【i】,f【i】【j】 = f【i-1】【j-1】+1;
三者取最小为f【i】【j】的最终解。
时间复杂度O(n^2);空间复杂度O(n^2);
CODE

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;int f[1010][1010];char a[1010], b[1010];int m, n; int main() {    freopen("edit.in", "r", stdin);    freopen("edit.out", "w", stdout);    scanf("%s%s", &a, &b);    m = strlen(a); n = strlen(b);    for(int i = 1; i <= m; ++i) f[i][0] = i;    for(int i = 1; i <= n; ++i) f[0][i] = i;    for(int i = 1; i <= m; ++i)         for(int j = 1; j <= n; ++j)                 if(a[i-1] == b[j-1]) f[i][j] = f[i-1][j-1];        else f[i][j] = min(min(f[i-1][j], f[i][j-1]), f[i-1][j-1])+1;    printf("%d", f[m][n]);    return 0;}

硬币找零(coin)
题目:
在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资。我们应该注意到,人民币的硬币系统是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

我的题解:
500的规模纯搜必爆,最优解问题应该可以A*来做;
我的做法是dfs,设硬币数coin,与应找的钱还差lft,初始状态为0,T,末状态为ans,0,不能找零的最少硬币数为cant,flag为有没有解的标志,初始置为false;
读入面值数及各面值a【i】,排序;
改变搜索顺序:按面值从大到小的顺序枚举面值,使lft尽快达到0;
可行性剪枝:当前面值大于lft,剪枝;
最优化剪枝:coin + 向下取整(lft / 最大面值) >= ans,剪枝;
如果达到lft == 0,ans = min(ans,coin);flag = true;
如果某层递归中未进入下层递归,cant = min(cant,coin);
if(flag) 输出ans;else 输出cant;
然后自己造了几组大数据,瞬间出解,于是乎交上去了……
结果T了6个;自造的数据虽N=500,T接近10000,但量大不代表数据就强啊。
卡时,递归超过500000次就if(flag) 输出ans;else 输出cant;exit(0);于是过了。算个随机化算法吧。
CODE

#include<cstdio>#include<cstdlib>#include<algorithm>using namespace std;int n, t, a[501], ans = 1000000000, cant = 1000000000, times;bool flag;inline void init() {                scanf("%d%d", &n, &t);    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);    sort(a+1, a+n+1);}void dfs(int coin, int lft) {    if(lft == 0) {                      flag = true;        ans = min(coin, ans);        return;    }    if(coin+lft/a[n] >= ans) return;         bool b = false;                         for(int i = n; i >= 1; --i) if(a[i] <= lft) {        b = true;        ++times;                                if(times == 500000) {            if(flag) printf("%d", ans);            else printf("%d", cant);            exit(0);        }        dfs(coin+1, lft-a[i]);    }    if(!b) cant = min(coin, cant);}int main() {    freopen("coin.in", "r", stdin);    freopen("coin.out", "w", stdout);    init();    dfs(0, t);    if(flag) printf("%d", ans);    else printf("%d", cant);    return 0;}

正解:dp
很类似背包,是一个简化版。
设f【i】为应找钱数为i时所需的最少钱币数,有f【0】 = 0;
f【i】 = min{f【i-a【j】】}+1;i = 1->T,,j = 1->n;
可见无解情况也能用该方程得到正确解。
时间复杂度O(n*T);空间复杂度O(n+T);
代码很简单
CODE

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int INF = 707406378;int n, t, f[10001], a[501];int main() {    freopen("coin.in", "r", stdin);    freopen("coin.out", "w", stdout);    scanf("%d%d", &n, &t);    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);    sort(a+1, a+n+1);    memset(f, 127/3, sizeof(f));        //f为dp数组     f[0] = 0;    for(int i = 1; i <= t; ++i) {        //dp方程,f[i] = min(f[i-a[j]]+1)         int k = INF;        for(int j = 1; j <= n; ++j)            if(a[j] > i) break;            else k = min(k, f[i-a[j]]+1);        f[i] = k;    }    printf("%d", f[t]);    return 0;}
0 0
原创粉丝点击