2016.8.7测试解题报告(compages,weight,software)

这套题真是改到身心俱疲啊!!!(三个感叹号表示强调)昨天晚上我改weight改到早上3点钟终于证明我的思路是错误的……看了题解之后豁然开朗原来我与成功只有一步之遥。这是一套很有意思的试题,含金量非常高。

1.数字组合

题目描述:
在N个数中找出其和为M的若干个数。先读入正整数N(N*<100)和M(M*<10000), 再读入N个正数(可以有相同的数字,每个数字均在1000以内), 在这N个数中找出若干个数, 使它们的和是M, 把满足条件的数字组合都找出来以统计组合的个数,输出组合的个数(不考虑组合是否相同)。

思路:
这道题一开始真的不知道他要考什么,推了一个组合数之后发现,时间复杂度也并不是很高,所以说写了以一个搜索优化随随便便就A了。但是这道题的正解其实是一个背包问题。我先贴一波搜索优化的代码,有时间就把背包动归的代码贴出来。

代码:

/*
2016.8.8 BulaBulaCHN
*/
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
int n,m;
int ans;
int a[101];
int sum[101];
void dfs(int last,int now)
{
    if(now==m)
    {
        ans++;
        return;
    }
    if(sum[n]-sum[last-1]+now<m) return;
    for(int i=last;i<=n;i++)
    {
        if(now+a[i]>m) return ;
        dfs(i+1,now+a[i]);
    }
    return ;
}
int main()
{
    freopen("compages.in","r",stdin);
    freopen("compages.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
    dfs(1,0);
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

2.树的重量

题目描述:
2、树的重量(weight.cpp)
问题描述:
树可以用来表示物种之间的进化关系。一棵“进化树”是一个带边权的树,其叶节点表示一个物种,两个叶节点之间的距离表示两个物种的差异。现在,一个重要的问题是,根据物种之间的距离,重构相应的“进化树”。
令N={1..n},用一个N上的矩阵M来定义树T。其中,矩阵M满足:对于任意的i,j,k,有M[i,j]+M[j,k]>=M[i,k]。树T满足:
1.叶节点属于集合N;
2.边权均为非负整数;
3.dT(i,j)=M[i,j],其中dT(i,j)表示树上i到j的最短路径长度。
如下图,矩阵M描述了一棵树。

     0    5    9    12   8
     5    0    8    11   7
M=   9    8    0    5    1
     12   11   5    0    4
     8    7    1    4    0

树的重量是指树上所有边权之和。对于任意给出的合法矩阵M,它所能表示树的重量是惟一确定的,不可能找到两棵不同重量的树,它们都符合矩阵M。你的任务就是,根据给出的矩阵M,计算M所表示树的重量。下图是上面给出的矩阵M所能表示的一棵树,这棵树的总重量为15。
这里写图片描述

思路:
先确定一个固定点,不断的找图中与固定点距离最远的点O(n),然后删去与固定点距离最远的点,同时删去该点与其最近的内点的连边。确定最远的点:直接查找O(n);确定最近的内点的连边:若该点序号为i,则内点连边为(min(a[i][j]+a[i][k]-a[j][k])/2)j或k为那个固定点。每次删掉一个点、一条边的同时,把边长放入答案。

代码:

/*
2016.8.8BulaBulaCHN
*/
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
int n;
int a[40][40];
bool vis[40];
int ans=0;
int main()
{
    freopen("weight.in","r",stdin);
    freopen("weight.out","w",stdout);
    while(scanf("%d",&n)==1 && n)
    {
        ans=0;
        memset(vis,0,sizeof vis);
        for(int i=1;i<=n-1;i++)
            for(int j=i+1;j<=n;j++)
                scanf("%d",&a[i][j]),a[j][i]=a[i][j];
        int fixed=1;//设置一个固定点
        for(int k=n;k>=2;k--)
        {
            int farst=0;//最远点与固定点的距离
            int id;//最远点的序号
            for(int i=1;i<=n;i++)
                if(!vis[i] && a[fixed][i]>farst)
                {
                    farst=a[fixed][i];
                    id=i;
                }
            vis[id]=1;//标记已经被从图中删除
            int po;
            int len=2147483647;
            for(int i=1;i<=n;i++)
                if(!vis[i])
                {
                    po=(a[id][fixed]+a[id][i]-a[i][fixed])/2;
                    len=min(len,po);
                }//需找到达最近内点的边
            for(int i=1;i<=n;i++)
                if(!vis[i])
                a[i][id]-=len,a[id][i]-=len;//更新矩阵
            ans+=len;//统计答案
        }
        printf("%d\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

3.软件开发

题目描述:
有一个软件公司,做俩项目,俩项目必须同时交,也就是说完成时间是看最后一个,然后每个项目拆成m个模块…可以同时做不同的模块,做完就好了,有n个人,给出n个人分别完成第一个项目的一个模块和第二个项目模块的时间,问最少需要多少时间交项目。

思路:
考试的时候想到:一眼动规题……,然后就写了一个f[i][j][k]表示前i个人做j个a模块和k个b模块所需的最短时间,结果居然是五层循环啊五层循环!眼看着就T了的我并没有放弃,把代码删了之后开始想怎么二分验证答案。万万没想到的就是这道题那个一眼就T被我无情删掉的算法居然过了7个点……世态炎凉啊……

然而这道题的思路也是很简单:二分答案+dp验证,因为已经知道当前的答案时间只需要去验证,所以我们把状态设为:在mid时间以内,前i个人做j个a模块之后可以做多少个b模块,若f[n][m]>m则此解符合条件,向左查找,否则向右查找。动态转移方程为:f[i][j]=max(f[i][j],f[i-1][j-k]+(mid-a[i]*k)/b[i])。转移状态的时候要特别注意的是一定要把除了f[0][0]之外的状态赋上一个不可能出现的值(我赋的值就是-1),然后把f[0][0]的初始值赋为0,若f[i-1][j-k]未被更新处理过则它没有资格被用来更新下一个状态。因为只有从f[0][0]为起始状态更新出来的状态才是正确状态,这个状态是所有正确状态的源头,dp的时候一定要特别注意这点。

说了这么多,也得粘一波代码了,请读者注意预处理的部分。

代码:

/*
2016.8.8 BulaBulaCHN
*/
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
int n,m;
int a[105];
int b[105];
int f[105][105];
int ans=0;
int judge(int mid)
{
    memset(f,-1,sizeof f);
    f[0][0]=0;//把所有除了f[0][0]以外的状态赋上一个不可能会出现的值
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k<=j,a[i]*k<=mid;k++)
    {
        if(f[i-1][j-k]==-1) continue;//如果没从f[0][0]更新出来,则它没有资格更新答案
        f[i][j]=max(f[i][j],f[i-1][j-k]+(mid-a[i]*k)/b[i]);
    }
    return f[n][m];
}
int main()
{
    freopen("software.in","r",stdin);
    freopen("software.out","w",stdout);
    scanf("%d%d",&n,&m);
    int maxa=0,maxb=0;
    for(int i=1;i<=n;i++) scanf("%d%d",&a[i],&b[i]),maxa=max(maxa,a[i]),maxb=max(maxb,b[i]);
    int l=0;
    int r=(maxa+maxb)*m;//注意,这是可能出现的最大时间
    while(l<=r)
    {
        int mid=(l+r)/2;
        int po=judge(mid);
        if(po>=m) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d",ans);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值