这套题真是改到身心俱疲啊!!!(三个感叹号表示强调)昨天晚上我改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;
}